Skip to content

Conversation

@osipxd
Copy link
Member

@osipxd osipxd commented Dec 5, 2025

Subsystem
ktor-io

Motivation
KTOR-9171 Redesign ByteReadChannel.readUTF8Line API

Fixes:

  • KTOR-9137 ByteReadChannel.readUTF8Line is inefficient for long lines
  • KTOR-4219 Make readUTF8LineTo return number of read symbols instead of boolean
  • KTOR-8945 ByteReadChannel.readUTF8Line doesn't throw TooLongLineException when the limit is reached

Solution

  • Replaced byte-by-byte scanning with indexOf
  • Optimized case when CR is not allowed (LF+CRLF mode)
  • Added readBuffer caching to reduce property access overhead
  • Performance scales with line length (larger lines see bigger gains)
Scenario 3.3.3 3.4.0 (Any) 3.4.0 (LF+CRLF) Speedup (best)
200k × 10B lines 20.18 ms 10.78 ms 9.09 ms 2.22×
1k × 1MB lines 3651.73 ms 823.30 ms 629.08 ms 5.85×

Benchmark: ktorio/ktor-benchmarks#87

I want to discuss if we should use LF + CRLF line ending mode by default. I think this mode should cover 99% of cases and it comes with performance boost. When we don't look for CR we can use only one indexOf so implementation becomes faster.

Currently readUTF8Line() and readUTF8LineTo() use LineEndingMode.Any by default and the variant of readUTF8LineTo taking lineEnding as a parameter is marked as @InternalAPI.

@osipxd osipxd requested a review from bjhham December 5, 2025 16:41
@osipxd osipxd self-assigned this Dec 5, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 5, 2025

Walkthrough

Replaces legacy UTF‑8 line readers with a new line-reading API (readLine, readLineStrict, readLineTo, readLineStrictTo) and adds a public LineEnding enum (Default, Lenient). Deprecates readUTF8Line* variants and updates HTTP parsing, client/server engines, socket code, and many tests to the new APIs.

Changes

Cohort / File(s) Summary
Core IO API & Impl
ktor-io/api/ktor-io.api, ktor-io/api/ktor-io.klib.api, ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt, ktor-io/common/src/io/ktor/utils/io/LineEnding.kt, ktor-io/common/src/io/ktor/utils/io/LineEndingMode.kt
Introduced readLine/readLineTo/readLineStrict/readLineStrictTo APIs and public enum LineEnding (Default, Lenient); added internal core internalReadLineTo; deprecated readUTF8Line* wrappers; updated API surface and docs.
HTTP parsing
ktor-http/ktor-http-cio/.../ChunkedTransferEncoding.kt, ktor-http/ktor-http-cio/.../HttpParser.kt
Replaced readUTF8LineTo usages with readLineStrictTo / readLine*; adapted EOF/return-value checks to new semantics.
Client engines, SSE & cache
ktor-client/ktor-client-core/.../DefaultClientSSESession.kt, ktor-client/ktor-client-core/jvm/.../FileCacheStorage.kt, ktor-client/ktor-client-apache5/jvm/test/.../Apache5HttpClientTest.kt, ktor-client/ktor-client-curl/desktop/.../CurlClientEngine.kt
Switched line-reading to new APIs (readLine* / readLineWithSave); adjusted SSE parsing to use lenient/default endings and updated cache/engine parsing calls.
Client tests & engines
ktor-client/ktor-client-java/jvm/test/.../ResponseConsumerTest.kt, ktor-client/ktor-client-okhttp/jvm/test/.../OkHttpHttp2Test.kt, ktor-client/ktor-client-tests/.../HttpTimeoutTest.kt, .../LoggingMockedTests.kt, .../HttpClientTest.kt
Tests updated to use readLine/readLineStrict variants; some test refactors (StringBuilder, readLineStrictTo) and import cleanups.
Core IO tests & coverage
ktor-io/common/test/ByteReadChannelOperationsTest.kt, ktor-io/common/test/ReadLineTest.kt, ktor-io/common/test/ReadUtf8LineTest.kt, ktor-io/jvm/test/ByteReadChannelOperationsJvmTest.kt
Added comprehensive ReadLineTest, renamed/updated existing UTF-8 line tests, removed legacy perf test, and migrated tests to new APIs.
Network socket tests
ktor-network/.../TCPSocketTest.kt, .../UnixSocketTest.kt, .../ClientSocketTest.kt, .../ServerSocketTest.kt
Replaced readUTF8Line calls with readLine/readLineStrict in socket communication tests.
Server CIO & pipeline tests
ktor-server/ktor-server-cio/.../CIOEngineTest.kt, .../CIOUnixSocketServerTest.kt, .../ServerPipelineTest.kt
Replaced readUTF8Line with readLineStrict for status, headers, and body reads; updated imports and socket test setup.
Server Netty, plugins, test-host, partial content
ktor-server/ktor-server-netty/.../NettySpecificTest.kt, ktor-server/ktor-server-plugins/.../RequestBodyLimitTest.kt, ktor-server/ktor-server-test-host/.../TestApplicationTest.kt, ktor-server/ktor-server-tests/.../ApplicationRequestContentTest.kt, ktor-server/ktor-server-tests/jvm/.../PartialContentTest.kt
Tests migrated to readLine/readLineStrict; import cleanups and minor exception/import adjustments.
Misc / infra tests
ktor-test-dispatcher/common/src/TestCommon.kt, ktor-utils/jvm/test/.../DeflaterReadChannelTest.kt
Replaced wildcard imports with explicit imports; added @Ignore to a flaky test.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Rationale: large, cross-cutting public API additions (new enum and multiple suspend APIs) plus wide sweep of call-site migrations. Many files changed with a consistent pattern but several critical areas require careful validation.
  • Areas needing extra attention:
    • ByteReadChannelOperations.kt — internalReadLineTo implementation, limits, lenient vs default semantics, deprecation wrappers and ABI compatibility.
    • API surface (ktor-io/api and klib entries) — ensure signatures, overloads, and enum export are correct for all targets.
    • HTTP parser and chunked transfer code — correctness on EOF, chunk terminator detection, and error conditions.
    • SSE/session and cache changes — verify reconnection, event parsing, and saved-line semantics remain intact.
    • Tests that changed behavior from lenient to strict (or vice versa) — validate expectations for CR/CRLF/LF handling.

Possibly related PRs

Suggested labels

👍 ship!

Suggested reviewers

  • bjhham
  • e5l
  • zibet27

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly references the main objective (KTOR-9171 redesign) and is specific about what changed (ByteReadChannel.readUTF8Line API).
Description check ✅ Passed The description follows the required template with Subsystem and Motivation sections, references the issue tracker, explains the solution with benchmarks, and addresses multiple related issues.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch osipxd/optimize-read-line

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

}

@Test
fun `test reading long lines completes in a reasonable time`() = runTest(timeout = 5.seconds) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test times out. It seems too low timeout for CI agent.
Probably we should remove these two tests and just run io-benchmarks 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the CI has proven itself to be unreasonable 😅

Comment on lines 502 to 507
val lfIndex = if (lineEnding != LineEndingMode.CR) readBuffer.indexOf(LF) else -1
val crIndex = if (allowsCR) {
// Index of lone CR delimiter.
// We're interested in it only if the requested line ending mode allows CR as a line delimiter.
val crEndIndex = if (lfIndex >= 0L) (lfIndex - crEndIndexCorrection).coerceAtLeast(0) else Long.MAX_VALUE
readBuffer.indexOf(CR, endIndex = crEndIndex)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handling different line ending modes turned out to be quite annoying. I hope the code is clear with comments. Please point out any unclear moments.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda looks like it may be a bit cleaner if we just had when(lineEnding) { ... } with separate functions for each strategy, then we can use the direct calls internally for an optimisation where relevant.

Copy link
Member Author

@osipxd osipxd Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean create a when with 7 branches? (CR + LF + CRLF, CR + LF, CR + CRLF, LF + CRLF, CR, LF, CRLF). It's sad that I've introduced such a flexible parameter, I think some of these combinations don't make any sense 😅

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah that would be a bit unwieldy. I thought it might be like 4 branches 😅

Maybe it would make sense to just have a function for CRLF + LF, then keep this more generous implementation for all other branches - then we call the CRLF + LF one directly for the better performance.

@osipxd osipxd force-pushed the osipxd/optimize-read-line branch from e396244 to ab9e968 Compare December 11, 2025 00:18
Comment on lines +669 to 686
fun transferString(count: Long) {
if (count > 0L) {
val string = readBuffer.readString(count)
out.append(string)
consumed += string.length
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could avoid one extra copy here if we introduce a Source.readTo(Appendable) function in kotlinx-io. Maybe we can make a contribution for the next release. If we wanted to do this without changing kotlinx-io it seems that would require a fair bit of duplication because most of their functions are internal in this area.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, also thought about it. My plan was to implement readStringTo inside Ktor and measure it's performance compared to readString. Then go to kotlinx-io guys with this data

Copy link
Member Author

@osipxd osipxd Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created an issue for further improvements: KTOR-9178

Comment on lines 694 to 728
val lfIndex = readBuffer.indexOf(LF, endIndex = limitLeft)

if (lenientLineEnding) {
val crEndIndex = when (lfIndex) {
// Subtract 1 from source.remaining to ignore the last byte,
// when RC might be part of a CRLF sequence split into two buffers
-1L -> minOf(limitLeft, readBuffer.remaining - 1)
0L -> 0
// Subtract 1 from lfIndex to ignore the case when CR is part of the CRLF sequence
else -> lfIndex - 1
}
if (lineBuffer.size >= max) {
throw TooLongLineException("Line exceeds limit of $max characters")
val crIndex = readBuffer.indexOf(CR, endIndex = crEndIndex)

// Sole CR in the buffer
if (crIndex >= 0) {
transferString(count = crIndex)
readBuffer.discard(1)
return consumed
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be performing two scans of the buffer here... Maybe it would be better to implement a custom Source.indexOf() for the two different bytes for the CR / LF case. I think it would be cleaner to just have two separate functions instead of including the CR only case inside this one. There'll be a bit of duplication, but it should be easier to follow.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, initially I made two different functions, but it was hard to fix bugs in two places 😅 And in this case we would need to duplicate all tests for both line ending modes to verify these functions actually behave similarly.
So I thought that if this extra logic for CR handling was guarded with a simple if it would be easy to follow the logic ignoring these 'if's for no-CR case.
Do you think we should split these functions?

Copy link
Member Author

@osipxd osipxd Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be better to implement a custom Source.indexOf() for the two different bytes for the CR / LF case

Good idea! I'll try to do it in the scope of KTOR-9178

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we should split these functions?

Maybe if splitting them out isn't viable, you could try function composition?

Something like...

@OptIn(InternalAPI::class, InternalIoApi::class)
public suspend fun ByteReadChannel.readLineStrictTo(
    out: Appendable,
    limit: Long = Long.MAX_VALUE,
    lineEnding: LineEnding = LineEnding.Default,
): Long {
    require(limit >= 0) { "Limit ($limit) should be non-negative" }

    return when(lineEnding) {
        LineEnding.Default -> internalReadLineTo(
            out,
            limit,
            throwOnIncompleteLine = true,
            scanForCr = DefaultScan,
            discardCr = DefaultDiscard,
        )
        LineEnding.Lenient -> internalReadLineTo(
            out,
            limit,
            throwOnIncompleteLine = true,
            scanForCr = LenientScan,
            discardCr = LenientDiscard,
        )
    }
}

@OptIn(InternalAPI::class, InternalIoApi::class)
private suspend fun ByteReadChannel.internalReadLineTo(
    out: Appendable,
    limit: Long,
    throwOnIncompleteLine: Boolean,
    scanForCr: Source.(Long, Long) -> Long,
    discardCr: Source.() -> Boolean,
): Long

Then you could use inline / crossinline for slightly better performance 🤷

Copy link
Member Author

@osipxd osipxd Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean something like this?

// Default
scanForCr = { _, _ -> -1L }
discardCr = { false }

// Lenient
scanForCr = { lfIndex, limitLeft ->
    val endIndex = when (lfIndex) {
        // Subtract 1 from source.remaining to ignore the last byte,
        // when RC might be part of a CRLF sequence split into two buffers
        -1L -> minOf(limitLeft, remaining - 1)
        0L -> 0
        // Subtract 1 from lfIndex to ignore the case when CR is part of the CRLF sequence
        else -> lfIndex - 1
    }

    indexOf(CR, endIndex = endIndex)
}
discardCr = {
    discard(1)
    true
}

Maybe it will be enough to just introduce an inner function scanForCr?

fun Source.scanForCr(lfIndex: Long, limitLeft: Long): Long {
    // Don't scan for CR in default mode
    if (!lenientLineEnding) return -1L
    
    val endIndex = ...
    return indexOf(CR, endIndex = endIndex)
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can check the variant with an inner function in this commit: a9ac7a8

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's what I was thinking. Just to shift the branching up a higher level. It's fine with the checks inside the function, it just adds a little more complexity.

Copy link
Member Author

@osipxd osipxd Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've failed to make this function inline as inline functions don't support inner functions 😢
So I'd proceed with an inner scanForCr function. WDYT?

Comment on lines +669 to 686
fun transferString(count: Long) {
if (count > 0L) {
val string = readBuffer.readString(count)
out.append(string)
consumed += string.length
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, also thought about it. My plan was to implement readStringTo inside Ktor and measure it's performance compared to readString. Then go to kotlinx-io guys with this data

Comment on lines 694 to 728
val lfIndex = readBuffer.indexOf(LF, endIndex = limitLeft)

if (lenientLineEnding) {
val crEndIndex = when (lfIndex) {
// Subtract 1 from source.remaining to ignore the last byte,
// when RC might be part of a CRLF sequence split into two buffers
-1L -> minOf(limitLeft, readBuffer.remaining - 1)
0L -> 0
// Subtract 1 from lfIndex to ignore the case when CR is part of the CRLF sequence
else -> lfIndex - 1
}
if (lineBuffer.size >= max) {
throw TooLongLineException("Line exceeds limit of $max characters")
val crIndex = readBuffer.indexOf(CR, endIndex = crEndIndex)

// Sole CR in the buffer
if (crIndex >= 0) {
transferString(count = crIndex)
readBuffer.discard(1)
return consumed
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, initially I made two different functions, but it was hard to fix bugs in two places 😅 And in this case we would need to duplicate all tests for both line ending modes to verify these functions actually behave similarly.
So I thought that if this extra logic for CR handling was guarded with a simple if it would be easy to follow the logic ignoring these 'if's for no-CR case.
Do you think we should split these functions?

Comment on lines 694 to 728
val lfIndex = readBuffer.indexOf(LF, endIndex = limitLeft)

if (lenientLineEnding) {
val crEndIndex = when (lfIndex) {
// Subtract 1 from source.remaining to ignore the last byte,
// when RC might be part of a CRLF sequence split into two buffers
-1L -> minOf(limitLeft, readBuffer.remaining - 1)
0L -> 0
// Subtract 1 from lfIndex to ignore the case when CR is part of the CRLF sequence
else -> lfIndex - 1
}
if (lineBuffer.size >= max) {
throw TooLongLineException("Line exceeds limit of $max characters")
val crIndex = readBuffer.indexOf(CR, endIndex = crEndIndex)

// Sole CR in the buffer
if (crIndex >= 0) {
transferString(count = crIndex)
readBuffer.discard(1)
return consumed
}
}
Copy link
Member Author

@osipxd osipxd Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be better to implement a custom Source.indexOf() for the two different bytes for the CR / LF case

Good idea! I'll try to do it in the scope of KTOR-9178

if (consumed == 0L && isClosedForRead) return -1

// Defensive check. Normally the consumed count should never exceed the limit
if (consumed > limit) throwTooLongLineException(limit)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question for review: Maybe IllegalStateException should be thrown here, I'm not sure

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, looks like a good place for a check().

Comment on lines +598 to +599
public suspend fun ByteReadChannel.readLineTo(
out: Appendable,
lineEnding: LineEnding = LineEnding.Default
): Long {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed limit from non-strict readLineTo as it was not obvious what to do when we reach the limit. We shouldn't throw, but returning a truncated string doesn't make sense. It also aligns with kotlinx-io.

@OptIn(InternalAPI::class, InternalIoApi::class)
public suspend fun ByteReadChannel.readLineStrictTo(
out: Appendable,
limit: Long = Long.MAX_VALUE,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question for review: Changed the type of limit to Long as kotlinx-io operates with long. However I'm not sure if it's good or bad for the API. In one hand we don't limit users with Int.MAX_VALUE, but in other hand this might require adding .toLong() in existing code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ok with Long here. Best to keep it consistent, though I can't imagine anyone using a value outside the max integer range.

@osipxd osipxd force-pushed the osipxd/optimize-read-line branch 4 times, most recently from aed91ae to f963802 Compare December 11, 2025 16:22
@osipxd osipxd changed the title KTOR-9137 Improve ByteReadChannel.readUTF8Line performance KTOR-9171 Redesign ByteReadChannel.readUTF8Line AP Dec 12, 2025
@osipxd osipxd changed the title KTOR-9171 Redesign ByteReadChannel.readUTF8Line AP KTOR-9171 Redesign ByteReadChannel.readUTF8Line API Dec 12, 2025
@osipxd osipxd force-pushed the osipxd/optimize-read-line branch 2 times, most recently from 1e181b7 to f8cfa5f Compare December 12, 2025 15:32
@osipxd osipxd marked this pull request as ready for review December 12, 2025 16:17
Comment on lines 476 to +515
/**
* Reads a line of UTF-8 characters to the specified [out] buffer.
* It recognizes CR, LF and CRLF as a line delimiter.
* It recognizes CR, LF, and CRLF as a line delimiter.
*
* ## Deprecation Notes
*
* This function is deprecated in favor of [readLineStrictTo] and [readLineTo].
*
* Use [readLineStrictTo] when:
* - Need to limit line length and throw [TooLongLineException] if the limit is reached
* - Expect an explicit line break at the end of a line. [EOFException] will be thrown
* if the channel is closed before the line break
*
* Use [readLineTo] when:
* - No need to limit line length
* - An explicit line break at the end of the channel is not mandatory
*
* Other changes:
* - New functions recognize LF and CRLF as a line delimiter by default. This default comes with better performance.
* To keep current behavior and recognize all line delimiters (CR, LF, and CRLF), specify [LineEnding.Lenient].
* - New functions return number of appended characters instead of [Boolean] or `-1` if the channel is empty.
* - [readLineStrictTo] accepts [Long] instead of [Int] as a limit parameter.
*
* The direct equivalent of `readUTF8LineTo` would be:
* ```
* // Before
* val success = channel.readUTF8LineTo(out, max = 1024)
*
* // After
* val success = try {
* channel.readLineStrictTo(out, limit = 1024, lineEnding = LineEnding.Lenient) >= 0
* } catch (_: EOFException) {
* true
* }
* ```
* However, we recommend to use [LineEnding.Default] if possible and verify if the case with
* the unexpected end of line should actually be ignored. We expect the following code to be correct in most cases:
* ```
* val success = channel.readLineTo(out, limit = 1024) >= 0
* ```
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bjhham, @vnikolova, could you check KDocs here? I suspect they're not actionable enough, but I don't how to improve yet keep it simple

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like enough information for me. It might be good to link to the relevant kotlinx-io function docs too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggested some small improvements for added clarity and actionability, but it generally looks good as it is!

Comment on lines +420 to +470
/**
* Reads a line of UTF-8 characters from the `ByteReadChannel`.
* It recognizes CR, LF, and CRLF as a line delimiter.
*
* ## Deprecation Notes
*
* This function is deprecated in favor of [readLineStrict] and [readLine].
*
* Use [readLineStrict] when:
* - Need to limit line length and throw [TooLongLineException] if the limit is reached
* - Expect an explicit line break at the end of a line. [EOFException] will be thrown
* if the channel is closed before the line break
*
* Use [readLine] when:
* - No need to limit line length
* - An explicit line break at the end of the channel is not mandatory
*
* Other changes:
* - New functions recognize LF and CRLF as a line delimiter by default. This default comes with better performance.
* To keep current behavior and recognize all line delimiters (CR, LF, and CRLF), specify [LineEnding.Lenient].
* - [readLineStrict] accepts [Long] instead of [Int] as a limit parameter.
*
* The direct equivalent of `readUTF8Line` would be:
* ```
* // Before
* val line = channel.readUTF8Line(max = 1024)
*
* // After
* val buffer = StringBuilder()
* val success = try {
* channel.readLineStrictTo(out, limit = 1024, lineEnding = LineEnding.Lenient) >= 0
* } catch (_: EOFException) {
* true
* }
* val line = if (success) buffer.toString() else null
* ```
* However, we recommend to use [LineEnding.Default] if possible and verify if the case with
* the unexpected end of line should actually be ignored. We expect the following code to be correct in most cases:
* ```
* val line = channel.readLineStrict(limit = 1024)
* ```
*
* [Report a problem](https://ktor.io/feedback/?fqname=io.ktor.utils.io.readUTF8Line)
*
* @param max the maximum number of characters to read. Default is [Int.MAX_VALUE].
* @return a string containing the line read, or null if channel is closed
* @throws TooLongLineException if max is reached before encountering a line delimiter or end of input
*/
@Suppress("DEPRECATION")
@Deprecated("Use readLineStrict instead. See deprecation notes for more details.")
public suspend fun ByteReadChannel.readUTF8Line(max: Int = Int.MAX_VALUE): String? {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These KDoc are almost similar to the first one, but for a function returning String.

Comment on lines +584 to +594
/**
* Reads a line of UTF-8 characters to the specified [out] buffer.
*
* Appends UTF-8 characters until a line break is found or the channel is exhausted. Implicit line break
* is assumed if the channel ends without a line break. Line break characters are not included in the result.
*
* @param out the buffer to append line to (line break characters are not appended)
* @param lineEnding line ending mode. Accepts LF and CRLF by default.
*
* @return number of characters appended to [out], or `-1` if can't read from the channel
*/
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here

Comment on lines +632 to +652
/**
* Reads a line of UTF-8 characters to the specified [out] buffer.
* Throws an exception if the line exceeds [limit] or doesn't end with a line break.
*
* The [limit] counts **characters** after UTF-8 decoding, not bytes.
* Line break characters are not included in the result and not counted towards the [limit].
* So this function may scan up to `limit + 2` characters (when line ends with CRLF after limit).
*
* **Important**: This function consumes bytes from the channel and appends them to [out]
* even if it fails to complete.
*
* @param out the buffer to append line to (line break characters are not appended)
* @param limit maximum characters to append to [out]. Unlimited by default.
* @param lineEnding line ending mode. Accepts LF and CRLF by default.
*
* @return number of characters appended to [out], or `-1` if can't read from the channel
* @throws TooLongLineException if the line exceeds [limit]
* @throws EOFException if the channel is closed before a line break is found
*/
@OptIn(InternalAPI::class, InternalIoApi::class)
public suspend fun ByteReadChannel.readLineStrictTo(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ktor-test-dispatcher/common/src/TestCommon.kt (1)

41-50: Avoid context name shadowing + consider preserving non-dispatcher context elements.

  • Line 46 shadows the context parameter with val context = ..., which makes this helper harder to reason about.
  • Also, the current logic discards the caller’s context entirely unless the dispatcher is a TestDispatcher. If the caller passed CoroutineName, MDC context, etc., those get dropped unexpectedly.

Suggested tweak (rename + keep extras while normalizing dispatcher):

 public fun runTestWithRealTime(
     context: CoroutineContext = EmptyCoroutineContext,
     timeout: Duration = 60.seconds,
     testBody: suspend CoroutineScope.() -> Unit
 ): TestResult {
-    val context = if (context[CoroutineDispatcher] is TestDispatcher) context else EmptyCoroutineContext
-    return runTest(context, timeout) {
+    val runTestContext =
+        if (context[CoroutineDispatcher] is TestDispatcher) context else context.minusKey(CoroutineDispatcher)
+    return runTest(runTestContext, timeout) {
         withContext(Dispatchers.Default.limitedParallelism(1), testBody)
     }
 }

If you intended to hard-reset all context elements (not just dispatcher), please add a short comment stating why. (Based on coding guidelines: keep API behavior intentional and well-documented.)

♻️ Duplicate comments (2)
ktor-io/common/test/ReadLineTest.kt (1)

17-73: Potential CI flakiness if writer {} escapes runTest scheduler (real delay(100)).
Given earlier feedback about timeouts in this area, please double-check these tests don’t sleep in real time on CI. If they do, prefer advancing virtual time (or structuring the test without time-based delays).

Also applies to: 165-186

ktor-io/common/test/ReadUtf8LineTest.kt (1)

37-43: Watch for CI timeouts if these delay(100) calls become real-time.
Given prior review feedback on timeouts in this area, please confirm runTest virtual time is actually driving these writers on CI; otherwise restructure to avoid wall-clock delays.

Also applies to: 66-80

🧹 Nitpick comments (5)
ktor-server/ktor-server-cio/common/test/io/ktor/tests/server/cio/CIOEngineTest.kt (1)

93-96: readLineStrict() usage is appropriate, but this test now assumes the 100-Continue response has no headers.
If a Date/other header ever gets added to the 100 Continue response, assertEquals("", readChannel.readLineStrict()) will fail because the next line wouldn’t be blank. Consider either (a) reading until the first blank line, or (b) explicitly asserting there are no headers (if that’s a protocol/implementation guarantee you want to lock in).

ktor-server/ktor-server-tests/common/test/io/ktor/tests/server/http/ApplicationRequestContentTest.kt (1)

21-23: Keep kotlin.test imports consistent with the module’s conventions
If this repo/module typically uses import kotlin.test.*, consider matching that to reduce churn; otherwise this is fine.

ktor-io/common/test/ReadLineTest.kt (1)

312-327: assertLines() assumes returned count matches Kotlin String.length (UTF-16 code units).
If the API contract is “number of read symbols” (per PR motivation), consider adding at least one non-ASCII line (e.g., , emoji, combining marks) to pin down the intended counting semantics.

ktor-client/ktor-client-apache5/jvm/test/io/ktor/client/engine/apache5/Apache5HttpClientTest.kt (1)

40-46: Consider looping on readLineStrict() result instead of isClosedForRead (avoids edge-case spin/hang).
Optional hardening:

-                while (!channel.isClosedForRead) {
-                    channel.readLineStrict()
-                }
+                while (channel.readLineStrict() != null) {
+                    // consume
+                }
ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt (1)

555-565: Fragile error detection in deprecated wrapper.

The check cause.message?.startsWith("Unexpected end of stream after reading") is brittle—if the message wording changes, this will break silently. Consider using a more robust detection mechanism or a specific exception subtype.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b148c65 and 3784489.

📒 Files selected for processing (36)
  • ktor-client/ktor-client-apache5/jvm/test/io/ktor/client/engine/apache5/Apache5HttpClientTest.kt (2 hunks)
  • ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/sse/DefaultClientSSESession.kt (3 hunks)
  • ktor-client/ktor-client-core/jvm/src/io/ktor/client/plugins/cache/storage/FileCacheStorage.kt (2 hunks)
  • ktor-client/ktor-client-curl/desktop/src/io/ktor/client/engine/curl/CurlClientEngine.kt (2 hunks)
  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt (3 hunks)
  • ktor-client/ktor-client-okhttp/jvm/test/io/ktor/client/engine/okhttp/OkHttpHttp2Test.kt (2 hunks)
  • ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/HttpTimeoutTest.kt (3 hunks)
  • ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/LoggingMockedTests.kt (1 hunks)
  • ktor-client/ktor-client-tests/jvm/src/io/ktor/client/tests/HttpClientTest.kt (1 hunks)
  • ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/ChunkedTransferEncoding.kt (2 hunks)
  • ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/HttpParser.kt (3 hunks)
  • ktor-http/ktor-http-cio/common/test/io/ktor/tests/http/cio/ResponseParserTest.kt (1 hunks)
  • ktor-http/ktor-http-cio/jvm/test/io/ktor/tests/http/cio/ChunkedTest.kt (6 hunks)
  • ktor-io/api/ktor-io.api (2 hunks)
  • ktor-io/api/ktor-io.klib.api (2 hunks)
  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt (4 hunks)
  • ktor-io/common/src/io/ktor/utils/io/LineEnding.kt (1 hunks)
  • ktor-io/common/src/io/ktor/utils/io/LineEndingMode.kt (1 hunks)
  • ktor-io/common/test/ByteReadChannelOperationsTest.kt (2 hunks)
  • ktor-io/common/test/ReadLineTest.kt (1 hunks)
  • ktor-io/common/test/ReadUtf8LineTest.kt (4 hunks)
  • ktor-io/jvm/test/ByteReadChannelOperationsJvmTest.kt (1 hunks)
  • ktor-network/common/test/io/ktor/network/sockets/tests/TCPSocketTest.kt (4 hunks)
  • ktor-network/common/test/io/ktor/network/sockets/tests/UnixSocketTest.kt (2 hunks)
  • ktor-network/jvm/test/io/ktor/network/sockets/tests/ClientSocketTest.kt (1 hunks)
  • ktor-network/jvm/test/io/ktor/network/sockets/tests/ServerSocketTest.kt (1 hunks)
  • ktor-server/ktor-server-cio/common/test/io/ktor/tests/server/cio/CIOEngineTest.kt (2 hunks)
  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOUnixSocketServerTest.kt (2 hunks)
  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/ServerPipelineTest.kt (2 hunks)
  • ktor-server/ktor-server-netty/jvm/test/io/ktor/tests/server/netty/NettySpecificTest.kt (2 hunks)
  • ktor-server/ktor-server-plugins/ktor-server-body-limit/common/test/io/ktor/server/plugins/bodylimit/RequestBodyLimitTest.kt (5 hunks)
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt (2 hunks)
  • ktor-server/ktor-server-tests/common/test/io/ktor/tests/server/http/ApplicationRequestContentTest.kt (3 hunks)
  • ktor-server/ktor-server-tests/jvm/test/io/ktor/server/plugins/PartialContentTest.kt (3 hunks)
  • ktor-test-dispatcher/common/src/TestCommon.kt (1 hunks)
  • ktor-utils/jvm/test/io/ktor/tests/utils/DeflaterReadChannelTest.kt (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.kt

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.kt: Follow Kotlin official style guide (https://kotlinlang.org/docs/coding-conventions.html)
Use star imports for io.ktor.* packages
Document all public APIs including parameters, return types, and exceptions
Mark internal APIs with @InternalAPI annotation
Run ./gradlew lintKotlin and fix all linting issues before giving control back to the user
Use ./gradlew formatKotlin to automatically fix formatting issues
Run ./gradlew updateLegacyAbi after making ABI changes to update ABI signature files
Binary compatibility is enforced - all public API changes must be tracked in the /api/ directories
Validate ABI with ./gradlew checkLegacyAbi and update with ./gradlew updateLegacyAbi
API changes must be intentional and well-documented
Error handling follows Kotlin conventions with specific Ktor exceptions

Files:

  • ktor-network/jvm/test/io/ktor/network/sockets/tests/ClientSocketTest.kt
  • ktor-client/ktor-client-curl/desktop/src/io/ktor/client/engine/curl/CurlClientEngine.kt
  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOUnixSocketServerTest.kt
  • ktor-utils/jvm/test/io/ktor/tests/utils/DeflaterReadChannelTest.kt
  • ktor-io/common/src/io/ktor/utils/io/LineEnding.kt
  • ktor-client/ktor-client-core/jvm/src/io/ktor/client/plugins/cache/storage/FileCacheStorage.kt
  • ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/HttpParser.kt
  • ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/LoggingMockedTests.kt
  • ktor-server/ktor-server-tests/jvm/test/io/ktor/server/plugins/PartialContentTest.kt
  • ktor-server/ktor-server-netty/jvm/test/io/ktor/tests/server/netty/NettySpecificTest.kt
  • ktor-server/ktor-server-tests/common/test/io/ktor/tests/server/http/ApplicationRequestContentTest.kt
  • ktor-http/ktor-http-cio/jvm/test/io/ktor/tests/http/cio/ChunkedTest.kt
  • ktor-network/jvm/test/io/ktor/network/sockets/tests/ServerSocketTest.kt
  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/ServerPipelineTest.kt
  • ktor-test-dispatcher/common/src/TestCommon.kt
  • ktor-server/ktor-server-plugins/ktor-server-body-limit/common/test/io/ktor/server/plugins/bodylimit/RequestBodyLimitTest.kt
  • ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/ChunkedTransferEncoding.kt
  • ktor-io/common/src/io/ktor/utils/io/LineEndingMode.kt
  • ktor-client/ktor-client-apache5/jvm/test/io/ktor/client/engine/apache5/Apache5HttpClientTest.kt
  • ktor-client/ktor-client-okhttp/jvm/test/io/ktor/client/engine/okhttp/OkHttpHttp2Test.kt
  • ktor-io/common/test/ReadLineTest.kt
  • ktor-io/jvm/test/ByteReadChannelOperationsJvmTest.kt
  • ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/HttpTimeoutTest.kt
  • ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/sse/DefaultClientSSESession.kt
  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
  • ktor-network/common/test/io/ktor/network/sockets/tests/UnixSocketTest.kt
  • ktor-client/ktor-client-tests/jvm/src/io/ktor/client/tests/HttpClientTest.kt
  • ktor-io/common/test/ByteReadChannelOperationsTest.kt
  • ktor-server/ktor-server-cio/common/test/io/ktor/tests/server/cio/CIOEngineTest.kt
  • ktor-http/ktor-http-cio/common/test/io/ktor/tests/http/cio/ResponseParserTest.kt
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt
  • ktor-network/common/test/io/ktor/network/sockets/tests/TCPSocketTest.kt
  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
**/test/**/*.kt

📄 CodeRabbit inference engine (CLAUDE.md)

Follow test naming pattern: DescribeWhatIsBeingTested

Files:

  • ktor-network/jvm/test/io/ktor/network/sockets/tests/ClientSocketTest.kt
  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOUnixSocketServerTest.kt
  • ktor-utils/jvm/test/io/ktor/tests/utils/DeflaterReadChannelTest.kt
  • ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/LoggingMockedTests.kt
  • ktor-server/ktor-server-tests/jvm/test/io/ktor/server/plugins/PartialContentTest.kt
  • ktor-server/ktor-server-netty/jvm/test/io/ktor/tests/server/netty/NettySpecificTest.kt
  • ktor-server/ktor-server-tests/common/test/io/ktor/tests/server/http/ApplicationRequestContentTest.kt
  • ktor-http/ktor-http-cio/jvm/test/io/ktor/tests/http/cio/ChunkedTest.kt
  • ktor-network/jvm/test/io/ktor/network/sockets/tests/ServerSocketTest.kt
  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/ServerPipelineTest.kt
  • ktor-server/ktor-server-plugins/ktor-server-body-limit/common/test/io/ktor/server/plugins/bodylimit/RequestBodyLimitTest.kt
  • ktor-client/ktor-client-apache5/jvm/test/io/ktor/client/engine/apache5/Apache5HttpClientTest.kt
  • ktor-client/ktor-client-okhttp/jvm/test/io/ktor/client/engine/okhttp/OkHttpHttp2Test.kt
  • ktor-io/common/test/ReadLineTest.kt
  • ktor-io/jvm/test/ByteReadChannelOperationsJvmTest.kt
  • ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/HttpTimeoutTest.kt
  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
  • ktor-network/common/test/io/ktor/network/sockets/tests/UnixSocketTest.kt
  • ktor-io/common/test/ByteReadChannelOperationsTest.kt
  • ktor-server/ktor-server-cio/common/test/io/ktor/tests/server/cio/CIOEngineTest.kt
  • ktor-http/ktor-http-cio/common/test/io/ktor/tests/http/cio/ResponseParserTest.kt
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt
  • ktor-network/common/test/io/ktor/network/sockets/tests/TCPSocketTest.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
🧠 Learnings (27)
📚 Learning: 2025-05-30T06:45:52.309Z
Learnt from: rururux
Repo: ktorio/ktor PR: 4896
File: ktor-client/ktor-client-core/jvm/test/FileStorageTest.kt:1-12
Timestamp: 2025-05-30T06:45:52.309Z
Learning: In Ktor test files, particularly in the ktor-client/ktor-client-core/jvm/test/ directory, test files follow the convention of not including explicit package declarations. This is consistent across test files like CachingCacheStorageTest.kt and should be maintained for consistency.

Applied to files:

  • ktor-network/jvm/test/io/ktor/network/sockets/tests/ClientSocketTest.kt
  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOUnixSocketServerTest.kt
  • ktor-utils/jvm/test/io/ktor/tests/utils/DeflaterReadChannelTest.kt
  • ktor-client/ktor-client-core/jvm/src/io/ktor/client/plugins/cache/storage/FileCacheStorage.kt
  • ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/LoggingMockedTests.kt
  • ktor-server/ktor-server-tests/jvm/test/io/ktor/server/plugins/PartialContentTest.kt
  • ktor-server/ktor-server-netty/jvm/test/io/ktor/tests/server/netty/NettySpecificTest.kt
  • ktor-server/ktor-server-tests/common/test/io/ktor/tests/server/http/ApplicationRequestContentTest.kt
  • ktor-http/ktor-http-cio/jvm/test/io/ktor/tests/http/cio/ChunkedTest.kt
  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/ServerPipelineTest.kt
  • ktor-test-dispatcher/common/src/TestCommon.kt
  • ktor-server/ktor-server-plugins/ktor-server-body-limit/common/test/io/ktor/server/plugins/bodylimit/RequestBodyLimitTest.kt
  • ktor-io/common/test/ReadLineTest.kt
  • ktor-io/jvm/test/ByteReadChannelOperationsJvmTest.kt
  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
  • ktor-client/ktor-client-tests/jvm/src/io/ktor/client/tests/HttpClientTest.kt
  • ktor-io/common/test/ByteReadChannelOperationsTest.kt
  • ktor-server/ktor-server-cio/common/test/io/ktor/tests/server/cio/CIOEngineTest.kt
  • ktor-http/ktor-http-cio/common/test/io/ktor/tests/http/cio/ResponseParserTest.kt
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt
  • ktor-network/common/test/io/ktor/network/sockets/tests/TCPSocketTest.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
📚 Learning: 2025-11-27T10:07:26.686Z
Learnt from: osipxd
Repo: ktorio/ktor PR: 5205
File: ktor-client/ktor-client-curl/desktop/interop/include/curl/multi.h:410-420
Timestamp: 2025-11-27T10:07:26.686Z
Learning: In the ktorio/ktor repository, do not review files under `ktor-client/ktor-client-curl/desktop/interop/include/curl/*` as these are upstream libcurl header files copied from the curl project and not maintained by the Ktor team.

Applied to files:

  • ktor-client/ktor-client-curl/desktop/src/io/ktor/client/engine/curl/CurlClientEngine.kt
  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOUnixSocketServerTest.kt
  • ktor-io/common/test/ByteReadChannelOperationsTest.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
📚 Learning: 2025-11-27T10:07:51.060Z
Learnt from: osipxd
Repo: ktorio/ktor PR: 5205
File: ktor-client/ktor-client-curl/libcurl/ports/curl/dependencies.patch:156-160
Timestamp: 2025-11-27T10:07:51.060Z
Learning: In the ktorio/ktor repository, do not review files under `ktor-client/ktor-client-curl/libcurl/ports/*` as these are vcpkg port files copied from the vcpkg repository and maintained upstream, not by the Ktor team.

Applied to files:

  • ktor-client/ktor-client-curl/desktop/src/io/ktor/client/engine/curl/CurlClientEngine.kt
📚 Learning: 2025-09-30T07:52:14.769Z
Learnt from: yschimke
Repo: ktorio/ktor PR: 4013
File: ktor-client/ktor-client-android/jvm/src/io/ktor/client/engine/android/Android14URLConnectionFactory.kt:24-26
Timestamp: 2025-09-30T07:52:14.769Z
Learning: In the Ktor Android HTTP client engine (ktor-client-android), prefer using `URI.create(urlString).toURL()` over the `URL(urlString)` constructor when opening connections with Android's HttpEngine, as it avoids deprecated APIs and the different exception behavior (IllegalArgumentException vs MalformedURLException) is acceptable.

Applied to files:

  • ktor-client/ktor-client-curl/desktop/src/io/ktor/client/engine/curl/CurlClientEngine.kt
  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
📚 Learning: 2025-06-23T12:49:56.883Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-06-23T12:49:56.883Z
Learning: Error handling should follow Kotlin conventions and use specific Ktor exceptions.

Applied to files:

  • ktor-client/ktor-client-curl/desktop/src/io/ktor/client/engine/curl/CurlClientEngine.kt
  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOUnixSocketServerTest.kt
  • ktor-server/ktor-server-tests/jvm/test/io/ktor/server/plugins/PartialContentTest.kt
  • ktor-server/ktor-server-netty/jvm/test/io/ktor/tests/server/netty/NettySpecificTest.kt
  • ktor-server/ktor-server-tests/common/test/io/ktor/tests/server/http/ApplicationRequestContentTest.kt
  • ktor-server/ktor-server-plugins/ktor-server-body-limit/common/test/io/ktor/server/plugins/bodylimit/RequestBodyLimitTest.kt
  • ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/sse/DefaultClientSSESession.kt
  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
  • ktor-io/common/test/ByteReadChannelOperationsTest.kt
  • ktor-server/ktor-server-cio/common/test/io/ktor/tests/server/cio/CIOEngineTest.kt
  • ktor-http/ktor-http-cio/common/test/io/ktor/tests/http/cio/ResponseParserTest.kt
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt
  • ktor-network/common/test/io/ktor/network/sockets/tests/TCPSocketTest.kt
  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
📚 Learning: 2025-11-25T09:38:19.393Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:38:19.393Z
Learning: Applies to **/*.kt : Error handling follows Kotlin conventions with specific Ktor exceptions

Applied to files:

  • ktor-client/ktor-client-curl/desktop/src/io/ktor/client/engine/curl/CurlClientEngine.kt
  • ktor-server/ktor-server-tests/jvm/test/io/ktor/server/plugins/PartialContentTest.kt
  • ktor-server/ktor-server-netty/jvm/test/io/ktor/tests/server/netty/NettySpecificTest.kt
  • ktor-server/ktor-server-tests/common/test/io/ktor/tests/server/http/ApplicationRequestContentTest.kt
  • ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/sse/DefaultClientSSESession.kt
  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
  • ktor-io/common/test/ByteReadChannelOperationsTest.kt
  • ktor-server/ktor-server-cio/common/test/io/ktor/tests/server/cio/CIOEngineTest.kt
  • ktor-http/ktor-http-cio/common/test/io/ktor/tests/http/cio/ResponseParserTest.kt
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt
  • ktor-network/common/test/io/ktor/network/sockets/tests/TCPSocketTest.kt
  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
📚 Learning: 2025-11-25T09:38:19.393Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:38:19.393Z
Learning: Applies to **/*.kt : Use star imports for `io.ktor.*` packages

Applied to files:

  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOUnixSocketServerTest.kt
  • ktor-server/ktor-server-tests/jvm/test/io/ktor/server/plugins/PartialContentTest.kt
  • ktor-server/ktor-server-netty/jvm/test/io/ktor/tests/server/netty/NettySpecificTest.kt
  • ktor-test-dispatcher/common/src/TestCommon.kt
  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
  • ktor-io/common/test/ByteReadChannelOperationsTest.kt
  • ktor-server/ktor-server-cio/common/test/io/ktor/tests/server/cio/CIOEngineTest.kt
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
📚 Learning: 2025-11-25T09:38:19.393Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:38:19.393Z
Learning: Applies to **/*.kt : API changes must be intentional and well-documented

Applied to files:

  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOUnixSocketServerTest.kt
  • ktor-utils/jvm/test/io/ktor/tests/utils/DeflaterReadChannelTest.kt
  • ktor-client/ktor-client-core/jvm/src/io/ktor/client/plugins/cache/storage/FileCacheStorage.kt
  • ktor-server/ktor-server-tests/jvm/test/io/ktor/server/plugins/PartialContentTest.kt
  • ktor-test-dispatcher/common/src/TestCommon.kt
  • ktor-io/common/src/io/ktor/utils/io/LineEndingMode.kt
  • ktor-io/jvm/test/ByteReadChannelOperationsJvmTest.kt
  • ktor-server/ktor-server-cio/common/test/io/ktor/tests/server/cio/CIOEngineTest.kt
  • ktor-io/api/ktor-io.klib.api
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt
  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
  • ktor-io/api/ktor-io.api
📚 Learning: 2025-05-16T13:11:28.416Z
Learnt from: osipxd
Repo: ktorio/ktor PR: 4860
File: ktor-server/ktor-server-plugins/ktor-server-di/api/ktor-server-di.api:24-26
Timestamp: 2025-05-16T13:11:28.416Z
Learning: Binary incompatible changes (such as constructor signature changes) are acceptable in the ktor-server-di module when the version is not yet released, as confirmed by the development team.

Applied to files:

  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOUnixSocketServerTest.kt
  • ktor-server/ktor-server-tests/jvm/test/io/ktor/server/plugins/PartialContentTest.kt
  • ktor-network/jvm/test/io/ktor/network/sockets/tests/ServerSocketTest.kt
  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/ServerPipelineTest.kt
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt
📚 Learning: 2025-05-14T18:05:02.321Z
Learnt from: bjhham
Repo: ktorio/ktor PR: 4855
File: ktor-server/ktor-server-plugins/ktor-server-di/api/ktor-server-di.klib.api:334-336
Timestamp: 2025-05-14T18:05:02.321Z
Learning: Breaking changes in constructor parameter order are acceptable for the ktor-server-di module when the code hasn't been released yet, as confirmed by the development team.

Applied to files:

  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOUnixSocketServerTest.kt
  • ktor-server/ktor-server-tests/jvm/test/io/ktor/server/plugins/PartialContentTest.kt
  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/ServerPipelineTest.kt
  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt
  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
📚 Learning: 2025-11-25T09:38:19.393Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:38:19.393Z
Learning: Applies to **/test/**/*.kt : Follow test naming pattern: `DescribeWhatIsBeingTested`

Applied to files:

  • ktor-utils/jvm/test/io/ktor/tests/utils/DeflaterReadChannelTest.kt
  • ktor-test-dispatcher/common/src/TestCommon.kt
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
📚 Learning: 2025-06-16T17:03:45.729Z
Learnt from: tre3p
Repo: ktorio/ktor PR: 4936
File: ktor-utils/jvm/src/io/ktor/util/ZstdEncoding.kt:28-35
Timestamp: 2025-06-16T17:03:45.729Z
Learning: In the Ktor codebase, encoder implementations consistently use GlobalScope.writer and GlobalScope.reader patterns for compression/decompression operations. This is the established architectural pattern used throughout the codebase for byte channel transformations.

Applied to files:

  • ktor-utils/jvm/test/io/ktor/tests/utils/DeflaterReadChannelTest.kt
  • ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/ChunkedTransferEncoding.kt
  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
  • ktor-io/common/test/ByteReadChannelOperationsTest.kt
  • ktor-io/api/ktor-io.klib.api
  • ktor-io/common/test/ReadUtf8LineTest.kt
📚 Learning: 2025-11-25T09:38:19.393Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:38:19.393Z
Learning: Applies to **/*.kt : Binary compatibility is enforced - all public API changes must be tracked in the `/api/` directories

Applied to files:

  • ktor-client/ktor-client-core/jvm/src/io/ktor/client/plugins/cache/storage/FileCacheStorage.kt
  • ktor-server/ktor-server-tests/jvm/test/io/ktor/server/plugins/PartialContentTest.kt
  • ktor-io/api/ktor-io.klib.api
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt
  • ktor-io/api/ktor-io.api
📚 Learning: 2025-06-09T07:08:35.085Z
Learnt from: bjhham
Repo: ktorio/ktor PR: 4916
File: ktor-server/ktor-server-core/api/ktor-server-core.api:151-151
Timestamp: 2025-06-09T07:08:35.085Z
Learning: Breaking changes are acceptable in Ktor codebase when the code hasn't been released yet, as confirmed by bjhham from the development team.

Applied to files:

  • ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/LoggingMockedTests.kt
  • ktor-server/ktor-server-netty/jvm/test/io/ktor/tests/server/netty/NettySpecificTest.kt
  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/ServerPipelineTest.kt
  • ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/ChunkedTransferEncoding.kt
  • ktor-client/ktor-client-okhttp/jvm/test/io/ktor/client/engine/okhttp/OkHttpHttp2Test.kt
  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
  • ktor-client/ktor-client-tests/jvm/src/io/ktor/client/tests/HttpClientTest.kt
  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
📚 Learning: 2025-08-14T15:17:11.466Z
Learnt from: zibet27
Repo: ktorio/ktor PR: 5044
File: ktor-client/ktor-client-webrtc/ktor-client-webrtc-rs/build.gradle.kts:12-12
Timestamp: 2025-08-14T15:17:11.466Z
Learning: The Cargo plugin (dev.gobley.cargo) used in the Ktor WebRTC RS module depends on the kotlin("plugin.atomicfu") plugin, so atomicfu should not be removed even if there are no direct kotlinx.atomicfu imports in the module's source code.

Applied to files:

  • ktor-server/ktor-server-tests/jvm/test/io/ktor/server/plugins/PartialContentTest.kt
📚 Learning: 2025-08-14T15:17:11.466Z
Learnt from: zibet27
Repo: ktorio/ktor PR: 5044
File: ktor-client/ktor-client-webrtc/ktor-client-webrtc-rs/build.gradle.kts:12-12
Timestamp: 2025-08-14T15:17:11.466Z
Learning: The Gobley Cargo plugin (dev.gobley.cargo) used in the Ktor WebRTC RS module requires the kotlin("plugin.atomicfu") plugin as a dependency, so the atomicfu plugin should not be removed even if there are no direct kotlinx.atomicfu imports in the module's source code.

Applied to files:

  • ktor-server/ktor-server-tests/jvm/test/io/ktor/server/plugins/PartialContentTest.kt
📚 Learning: 2025-09-05T12:47:49.016Z
Learnt from: bjhham
Repo: ktorio/ktor PR: 4887
File: ktor-server/ktor-server-jetty-jakarta/jvm/src/io/ktor/server/jetty/jakarta/JettyWebsocketConnection.kt:35-52
Timestamp: 2025-09-05T12:47:49.016Z
Learning: JettyWebsocketConnection in ktor-server-jetty-jakarta implements Closeable interface (has close() method), so connection.use { } is valid Kotlin syntax and will compile correctly.

Applied to files:

  • ktor-server/ktor-server-tests/jvm/test/io/ktor/server/plugins/PartialContentTest.kt
  • ktor-server/ktor-server-netty/jvm/test/io/ktor/tests/server/netty/NettySpecificTest.kt
  • ktor-server/ktor-server-tests/common/test/io/ktor/tests/server/http/ApplicationRequestContentTest.kt
  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
  • ktor-io/common/test/ByteReadChannelOperationsTest.kt
  • ktor-server/ktor-server-cio/common/test/io/ktor/tests/server/cio/CIOEngineTest.kt
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt
  • ktor-network/common/test/io/ktor/network/sockets/tests/TCPSocketTest.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
📚 Learning: 2025-05-14T18:17:03.059Z
Learnt from: bjhham
Repo: ktorio/ktor PR: 4855
File: ktor-server/ktor-server-plugins/ktor-server-di/common/src/io/ktor/server/plugins/di/DependencyRegistry.kt:37-38
Timestamp: 2025-05-14T18:17:03.059Z
Learning: When implementing CoroutineScope in Ktor components like DependencyRegistry, always ensure the scope is properly cancelled during application shutdown to prevent memory leaks and resource issues. In the DI plugin, this is handled by calling registry.cancel() in the ApplicationStopped event handler after all dependency cleanup is complete.

Applied to files:

  • ktor-server/ktor-server-tests/jvm/test/io/ktor/server/plugins/PartialContentTest.kt
📚 Learning: 2025-09-05T12:46:14.074Z
Learnt from: bjhham
Repo: ktorio/ktor PR: 4887
File: ktor-server/ktor-server-jetty-jakarta/jvm/src/io/ktor/server/jetty/jakarta/JettyWebsocketConnection.kt:90-100
Timestamp: 2025-09-05T12:46:14.074Z
Learning: The ByteReadChannel.readAvailable(ByteBuffer) method in Ktor IO automatically calls awaitContent() internally when the read buffer is exhausted. When it returns 0, it has already suspended and waited for data to become available, so adding explicit awaitContent() calls is redundant and incorrect.

Applied to files:

  • ktor-server/ktor-server-tests/jvm/test/io/ktor/server/plugins/PartialContentTest.kt
  • ktor-server/ktor-server-tests/common/test/io/ktor/tests/server/http/ApplicationRequestContentTest.kt
  • ktor-http/ktor-http-cio/jvm/test/io/ktor/tests/http/cio/ChunkedTest.kt
  • ktor-network/jvm/test/io/ktor/network/sockets/tests/ServerSocketTest.kt
  • ktor-server/ktor-server-plugins/ktor-server-body-limit/common/test/io/ktor/server/plugins/bodylimit/RequestBodyLimitTest.kt
  • ktor-client/ktor-client-apache5/jvm/test/io/ktor/client/engine/apache5/Apache5HttpClientTest.kt
  • ktor-io/jvm/test/ByteReadChannelOperationsJvmTest.kt
  • ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/sse/DefaultClientSSESession.kt
  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
  • ktor-io/common/test/ByteReadChannelOperationsTest.kt
  • ktor-server/ktor-server-cio/common/test/io/ktor/tests/server/cio/CIOEngineTest.kt
  • ktor-io/api/ktor-io.klib.api
  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
  • ktor-io/api/ktor-io.api
📚 Learning: 2025-05-30T06:45:52.309Z
Learnt from: rururux
Repo: ktorio/ktor PR: 4896
File: ktor-client/ktor-client-core/jvm/test/FileStorageTest.kt:1-12
Timestamp: 2025-05-30T06:45:52.309Z
Learning: The headersOf() function from io.ktor.http package is available through wildcard imports like `import io.ktor.http.*`, so no explicit import statement is needed when using wildcard imports from that package.

Applied to files:

  • ktor-server/ktor-server-tests/common/test/io/ktor/tests/server/http/ApplicationRequestContentTest.kt
  • ktor-io/common/test/ByteReadChannelOperationsTest.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
📚 Learning: 2025-11-14T14:11:30.292Z
Learnt from: osipxd
Repo: ktorio/ktor PR: 5195
File: ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/JavaHttp2Test.kt:13-15
Timestamp: 2025-11-14T14:11:30.292Z
Learning: In ktor-client-java tests, Java's HttpClient does support h2c (HTTP/2 cleartext) when configured with `protocolVersion = HttpClient.Version.HTTP_2` on Java 11+. The JavaHttp2Test successfully passes with the default useH2c=true setting, connecting to http://localhost:8084.

Applied to files:

  • ktor-client/ktor-client-okhttp/jvm/test/io/ktor/client/engine/okhttp/OkHttpHttp2Test.kt
  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
📚 Learning: 2025-06-16T17:13:12.657Z
Learnt from: tre3p
Repo: ktorio/ktor PR: 4936
File: ktor-utils/api/ktor-utils.api:498-506
Timestamp: 2025-06-16T17:13:12.657Z
Learning: The user tre3p removed ZstdInputStream/ZstdOutputStream from their ZstdEncoder implementation to address blocking I/O concerns in coroutine contexts.

Applied to files:

  • ktor-io/jvm/test/ByteReadChannelOperationsJvmTest.kt
📚 Learning: 2025-09-05T12:47:49.016Z
Learnt from: bjhham
Repo: ktorio/ktor PR: 4887
File: ktor-server/ktor-server-jetty-jakarta/jvm/src/io/ktor/server/jetty/jakarta/JettyWebsocketConnection.kt:35-52
Timestamp: 2025-09-05T12:47:49.016Z
Learning: Jetty's AbstractConnection class implements Closeable interface, so any class extending AbstractConnection (like JettyWebsocketConnection) can be used with Kotlin's use { } extension function for resource management.

Applied to files:

  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
  • ktor-io/common/test/ByteReadChannelOperationsTest.kt
  • ktor-network/common/test/io/ktor/network/sockets/tests/TCPSocketTest.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
📚 Learning: 2025-06-23T12:49:56.883Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-06-23T12:49:56.883Z
Learning: Prefix commit messages with KTOR-{NUM} (YouTrack issue), and write them in present tense, imperative mood.

Applied to files:

  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
📚 Learning: 2025-11-25T09:38:19.393Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:38:19.393Z
Learning: Prefix commit messages with KTOR-{NUM} (YouTrack issue number)

Applied to files:

  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
📚 Learning: 2025-07-01T10:54:53.751Z
Learnt from: osipxd
Repo: ktorio/ktor PR: 4970
File: build-logic/src/main/kotlin/ktorbuild.kmp.gradle.kts:67-72
Timestamp: 2025-07-01T10:54:53.751Z
Learning: In build-logic/src/main/kotlin/ktorbuild.kmp.gradle.kts, the KT-70915 and KT-77609 workarounds that limit parallelism for KotlinNativeLink and CInteropCommonizerTask should remain unconditional (not gated behind CI flag) as these limits are beneficial for both local and CI builds.

Applied to files:

  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
📚 Learning: 2025-11-25T09:38:19.393Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:38:19.393Z
Learning: Applies to **/*.kt : Document all public APIs including parameters, return types, and exceptions

Applied to files:

  • ktor-io/api/ktor-io.api
🧬 Code graph analysis (7)
ktor-client/ktor-client-curl/desktop/src/io/ktor/client/engine/curl/CurlClientEngine.kt (1)
ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt (1)
  • readLineStrict (624-630)
ktor-server/ktor-server-tests/jvm/test/io/ktor/server/plugins/PartialContentTest.kt (1)
ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt (1)
  • readLineStrict (624-630)
ktor-http/ktor-http-cio/jvm/test/io/ktor/tests/http/cio/ChunkedTest.kt (1)
ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/ChunkedTransferEncoding.kt (2)
  • decodeChunked (51-55)
  • decodeChunked (66-100)
ktor-io/common/test/ReadLineTest.kt (2)
ktor-server/ktor-server-test-suites/jvm/src/io/ktor/server/testing/suites/Utils.kt (1)
  • assertFailsWith (94-104)
ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt (3)
  • readLineTo (595-606)
  • readLineStrictTo (651-665)
  • exhausted (32-36)
ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/sse/DefaultClientSSESession.kt (1)
ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt (1)
  • readLine (577-582)
ktor-http/ktor-http-cio/common/test/io/ktor/tests/http/cio/ResponseParserTest.kt (3)
ktor-http/ktor-http-cio/common/test/io/ktor/tests/http/cio/HttpParserTestUtils.kt (1)
  • test (12-27)
ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/HttpParser.kt (1)
  • parseResponse (75-97)
ktor-server/ktor-server-test-suites/jvm/src/io/ktor/server/testing/suites/Utils.kt (1)
  • assertFailsWith (94-104)
ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt (4)
ktor-io/common/src/io/ktor/utils/io/ByteChannel.kt (1)
  • awaitContent (69-80)
ktor-io/common/src/io/ktor/utils/io/CountedByteReadChannel.kt (1)
  • awaitContent (43-53)
ktor-io/jvm/src/io/ktor/utils/io/jvm/javaio/Reading.kt (1)
  • awaitContent (66-87)
ktor-io/common/src/io/ktor/utils/io/core/ByteReadPacket.kt (1)
  • discard (69-75)
🔇 Additional comments (51)
ktor-test-dispatcher/common/src/TestCommon.kt (1)

7-16: Imports-only change is fine, but verify it matches the repo’s coroutines-test version.

This is “just” import hygiene, but it can surface mismatches if TestResult / runTest overloads differ across kotlinx-coroutines-test versions.

ktor-server/ktor-server-cio/common/test/io/ktor/tests/server/cio/CIOEngineTest.kt (1)

2-2: Copyright header year bump looks correct.

ktor-server/ktor-server-tests/common/test/io/ktor/tests/server/http/ApplicationRequestContentTest.kt (1)

203-208: Verify readLine() behavior when the body has no trailing newline
Here "bodyContent" doesn’t include \n. Please confirm call.receiveChannel().readLine() returns "bodyContent" (vs null, exception, or suspending waiting for a line ending) under the new API contract; otherwise this test may become flaky/hang depending on implementation and line-ending mode defaults.

ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/LoggingMockedTests.kt (1)

332-332: API migration looks correct.

The change from readUTF8Line() to readLineStrict() aligns with the PR's API redesign objectives. For this test's simple input ("Hello world!\n"), the stricter API should work identically while benefiting from the performance improvements mentioned in the PR.

ktor-network/common/test/io/ktor/network/sockets/tests/UnixSocketTest.kt (2)

48-51: Second readLineStrict() usage is consistent with test data and avoids nullable handling.
No additional concerns beyond verifying the strict EOF/terminator behavior matches the test’s assumptions.


39-41: Migration to readLineStrict() is correct and safe. The test writes \n-terminated lines with explicit flush() before closing the write channel, ensuring data is available when readLineStrict() is called. Since readLineStrict() requires a line ending and will throw EOFException if the channel closes before finding one, and the test data guarantees the line ending, there is no risk of null return or exceptions from premature closure.

ktor-network/jvm/test/io/ktor/network/sockets/tests/ServerSocketTest.kt (1)

74-82: The test code is correct as-is; readLine() treats EOF as an implicit line terminator.

The readLine() function explicitly handles EOF as an implicit line break (see ByteReadChannelOperations.kt: readLineTo with throwOnIncompleteLine = false). Sending "123" without \n works fine with readLine(). The distinction is that readLineStrict() requires an explicit line break and will throw EOFException if the channel closes without one—but this test uses readLine(), not readLineStrict().

ktor-utils/jvm/test/io/ktor/tests/utils/DeflaterReadChannelTest.kt (1)

21-21: Ensure KTOR-9179 is properly tracked before disabling the entire test class.

The @Ignore annotation disables all tests in DeflaterReadChannelTest. While referencing a JIRA ticket (KTOR-9179) for traceability is good practice, confirm that this ticket exists, is public, and has a defined resolution timeline. If this is the only ignored test suite in the project with race conditions, ensure the team has a plan to address and re-enable it.

ktor-client/ktor-client-okhttp/jvm/test/io/ktor/client/engine/okhttp/OkHttpHttp2Test.kt (2)

81-85: Exception-path read: readLineStrict() is a good match, but ensure it throws on EOF/close as expected

This test assumes the second out.readLineStrict() (Line 84) throws under duplex writer failure; if the new strict API returns null/empty on close instead of throwing, the fail(...) will trigger and the assertion will flip. Worth confirming the new readLineStrict() contract for closed channel/error propagation.


39-47: The concern about potential double-newline/delimiter mismatch is unfounded. The readLineStrictTo(buffer) function appends only the line content (without the line ending delimiter) to the buffer and returns the number of characters appended. The manual buffer.append('\n') at line 44 is correct and necessary to reconstruct proper line separation in the accumulated output. The test code works as intended with no double-newlines or delimiter mismatches.

Likely an incorrect or invalid review comment.

ktor-network/jvm/test/io/ktor/network/sockets/tests/ClientSocketTest.kt (1)

91-93: No action needed—readLine() correctly handles EOF without a line terminator. The API returns accumulated bytes when the channel closes, making the test assertion valid. The readLine() method is specifically designed for cases where "an explicit line break at the end of the channel is not mandatory," unlike readLineStrict() which throws EOFException in such scenarios.

Likely an incorrect or invalid review comment.

ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt (3)

2-3: Copyright header update looks fine.


12-23: Imports are now explicit; keep an eye on coroutines API compatibility.
If this repo pins a specific kotlinx-coroutines version, ensure kotlinx.coroutines.future.await is available for that version.


58-58: ByteReadChannel.readLine() returns String? and can return null on EOF. The test directly uses this result in assertEquals() without null-guarding, which will produce a clear failure message ("ktor" != null) if EOF is unexpectedly reached. For a unit test with known, controlled input, this is acceptable; however, if you prefer explicit null-handling for clarity, assign to a variable first and assert non-null before use.

ktor-server/ktor-server-netty/jvm/test/io/ktor/tests/server/netty/NettySpecificTest.kt (1)

216-216: Migration looks good. The change from readUTF8Line() to readLine() is the correct API update. The default LineEnding parameter recognizes both LF and CRLF, which is appropriate for parsing HTTP responses.

ktor-server/ktor-server-plugins/ktor-server-body-limit/common/test/io/ktor/server/plugins/bodylimit/RequestBodyLimitTest.kt (3)

2-2: LGTM: Copyright year updated.

Standard maintenance update to include 2025.


16-20: LGTM: Import refinements follow guidelines.

The explicit imports for kotlinx.coroutines.test and kotlin.test are appropriate, while io.ktor.* packages correctly use star imports above.


154-154: The API migration from readUTF8Line() to readLine() is correct and properly tested.

The three test methods correctly validate applyLimit() behavior across different scenarios:

  • channelApplyLimitTooLarge: Verifies PayloadTooLargeException is thrown when payload exceeds the 5-byte limit
  • channelApplyLimitNormal: Verifies successful read when payload (10 bytes) equals the limit
  • channelApplyLimitEmptyOnRead: Verifies null return when the channel closes after the first availability check (KTOR-7254)

The readLine() API properly replaces the deprecated readUTF8Line(). Both return String? and propagate exceptions from wrapped channels like applyLimit() without suppression. Test naming follows the DescribeWhatIsBeingTested convention as required.

ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOUnixSocketServerTest.kt (1)

7-15: Star imports for io.ktor.* look correct per repo conventions.
This aligns with the “use star imports for io.ktor.* packages” guideline.

ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/ServerPipelineTest.kt (1)

85-87: Assertions assume readLineStrict() normalizes CRLF (no trailing \r) and returns non-null here.
If readLineStrict() can surface a trailing \r for CRLF, these will intermittently fail (e.g., "HTTP/1.1 200 OK\r"). If its contract is “CRLF → strip both”, then this is solid.

Also applies to: 120-122

ktor-client/ktor-client-tests/jvm/src/io/ktor/client/tests/HttpClientTest.kt (1)

114-114: LGTM! SSE line reading updated to new API.

The migration from readUTF8Line() to readLineStrict() is appropriate for SSE parsing, which benefits from strict line ending enforcement.

Also applies to: 118-118

ktor-client/ktor-client-core/jvm/src/io/ktor/client/plugins/cache/storage/FileCacheStorage.kt (1)

225-244: LGTM! Cache deserialization updated to new line-reading API.

The migration to readLineStrict() with null-assertion operators is appropriate here. The structured binary format guarantees data availability through pre-read counts (headersCount, varyKeysCount), making the !! operators safe. The refactoring to repeat() blocks also improves readability.

ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/HttpParser.kt (1)

45-45: LGTM! HTTP parsing migrated to new line-reading API.

The migration from readUTF8LineTo() to readLineStrictTo() correctly adapts to the new return semantics (bytes read as Long with -1L for EOF). The use of strict line endings aligns with HTTP standards (RFC 9112), and line limit enforcement is preserved.

Also applies to: 81-81, 122-122

ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/HttpTimeoutTest.kt (1)

225-225: LGTM! Timeout tests updated to new line-reading API.

The migration from readUTF8Line() to readLine() is appropriate for these timeout tests. The lenient variant (readLine()) is suitable here since the tests focus on cancellation behavior rather than line ending validation.

Also applies to: 246-246, 265-265

ktor-io/jvm/test/ByteReadChannelOperationsJvmTest.kt (1)

6-15: LGTM! Test cleanup and import refinement.

The explicit imports improve code clarity, and the removal of the deprecated readUTF8LineTo test aligns with the API migration strategy.

ktor-io/common/src/io/ktor/utils/io/LineEnding.kt (1)

1-26: LGTM! New line ending mode API is well-designed.

The LineEnding enum provides a clean, type-safe API for configuring line ending behavior. The documentation clearly explains when to use each mode, and the recommendation to prefer Default for efficiency is helpful.

ktor-io/common/src/io/ktor/utils/io/LineEndingMode.kt (1)

18-19: LGTM! Deprecation path established.

The deprecation annotation appropriately guides users to the new LineEnding API. The @Suppress("DEPRECATION") is necessary for internal self-references within the deprecated class.

ktor-server/ktor-server-tests/jvm/test/io/ktor/server/plugins/PartialContentTest.kt (1)

138-138: LGTM! Multipart parsing updated to strict line reading.

The migration to readLineStrict() is appropriate for multipart boundary and header parsing, which requires strict line ending handling per the multipart format specification. The null checks correctly handle end-of-stream scenarios.

Also applies to: 145-145

ktor-io/common/test/ReadLineTest.kt (1)

16-310: Great coverage of CR/LF boundary + limit edge cases (esp. CRLF straddling buffers).
These scenarios map well to the PR’s correctness goals (limit handling, delimiter parsing, EOF vs strict).

ktor-client/ktor-client-apache5/jvm/test/io/ktor/client/engine/apache5/Apache5HttpClientTest.kt (1)

71-74: Switch to readLineStrict() in assertion looks consistent with the new API.

ktor-io/common/test/ReadUtf8LineTest.kt (2)

14-18: Nice separation of responsibilities between legacy readUTF8LineTo and new line APIs.
This makes it clearer what behavior is being preserved vs newly specified.


52-64: Good additions: CRLF-only flush case + “no line ending” regression coverage.
The LineEndingMode.CRLF test is especially valuable for preserving legacy behavior under the new implementation.

Also applies to: 82-93

ktor-io/api/ktor-io.klib.api (1)

50-59: ABI dump includes the new LineEnding + readLine* APIs — please verify all ABI checks + docs are in place.
Per repo guidelines, ensure checkLegacyAbi is clean and the new public APIs are documented (params/return/throws) in source.

Also applies to: 503-506

ktor-io/api/ktor-io.api (2)

319-325: New LineEnding enum API looks well-structured.

The enum with Default and Lenient entries aligns with the PR objectives. Default (LF+CRLF only) enables the performance optimization, while Lenient (CR+LF+CRLF) maintains backward compatibility.


88-95: New line-reading APIs properly tracked in ABI.

The new readLine, readLineStrict, readLineStrictTo, and readLineTo functions with their $default synthetic variants are correctly declared. This maintains binary compatibility while introducing the new API surface. Based on learnings, API changes must be intentional and well-documented, and binary compatibility is enforced.

ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt (4)

420-474: Comprehensive deprecation documentation.

The detailed migration guide in the deprecation notice is helpful. It clearly explains when to use readLineStrict vs readLine and provides code examples for direct equivalents. This helps users migrate their code correctly.


595-606: Clean delegation to internal implementation.

The readLineTo function correctly delegates with throwOnIncompleteLine = false, matching the documented behavior of accepting implicit line breaks at EOF.


651-665: Proper validation and clear semantics.

The require(limit >= 0) validation follows Kotlin conventions. Documentation correctly notes that the limit counts characters after UTF-8 decoding, which is important for users.


783-789: Helper functions correctly return Nothing.

The exception helper functions throwTooLongLineException and throwEndOfStreamException correctly have Nothing return type, making the control flow clear to the compiler.

ktor-network/common/test/io/ktor/network/sockets/tests/TCPSocketTest.kt (2)

58-58: Appropriate use of readLineStrict().

Using readLineStrict() is correct here since the server explicitly writes \n-terminated lines. The strict variant will throw if the connection closes unexpectedly without a line break, which is the desired behavior for these tests.


310-316: Consistent migration in autoFlush test.

The test correctly uses readLineStrict() for reading server responses with explicit line terminators.

ktor-io/common/test/ByteReadChannelOperationsTest.kt (1)

291-291: Correct use of non-strict readLine().

Using readLine() instead of readLineStrict() is appropriate here because the remaining content "skipIfFound" has no trailing newline, and readLine() correctly handles implicit line breaks at EOF.

ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt (1)

523-526: Migration to readLineStrict() is appropriate.

The server sends \n-terminated lines, so readLineStrict() is correct. The ?: break handles the case where readLineStrict() returns null (channel empty), though with proper line terminators this shouldn't occur before isClosedForRead becomes true.

ktor-client/ktor-client-curl/desktop/src/io/ktor/client/engine/curl/CurlClientEngine.kt (1)

38-40: LGTM! API migration is correct.

The change from readUTF8Line() to readLineStrict() correctly migrates to the new line-reading API. Both functions return String? and the nullable result is appropriately discarded since the intent is to skip the status line as a side effect.

ktor-http/ktor-http-cio/jvm/test/io/ktor/tests/http/cio/ChunkedTest.kt (2)

55-55: LGTM! Test code simplified.

The change from ByteReadChannel(bodyText.toByteArray()) to ByteReadChannel(bodyText) is a good simplification that leverages the String convenience constructor while maintaining identical functionality.

Also applies to: 66-66, 76-76, 88-88, 101-101, 112-112, 123-123, 138-138


106-106: LGTM! API migration is correct.

The change from readUTF8Line() to readLine() correctly migrates to the new line-reading API as intended by this PR.

Also applies to: 117-117, 128-128

ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/ChunkedTransferEncoding.kt (2)

72-72: LGTM! Correct API migration.

The change from readUTF8LineTo() (returns Boolean) to readLineStrictTo() (returns Long) with a >= 0 check correctly adapts to the new API semantics where -1 indicates EOF and non-negative values indicate successful line reads.


86-89: The end-of-chunk line validation semantics are correct.

Verification confirms that readLineStrictTo(chunkSizeBuffer, limit = 0):

  • Returns 0 when an empty line (just CRLF or LF) is encountered
  • Throws TooLongLineException if any content characters appear before the line ending
  • Returns -1L only on EOF

This properly validates that the chunk trailer contains no content, enforcing the HTTP chunked transfer encoding format where each chunk must be followed by an empty line.

ktor-http/ktor-http-cio/common/test/io/ktor/tests/http/cio/ResponseParserTest.kt (3)

9-11: LGTM! Explicit imports improve clarity.

The change from wildcard import to explicit imports for kotlin.test functions follows Kotlin best practices and makes the test dependencies clear.


15-15: LGTM! Consistent use of test helper.

The migration to the test helper function (from HttpParserTestUtils.kt) provides cleaner syntax for suspend test functions and is consistent with other tests in this package.

Also applies to: 26-26, 39-39, 46-46


17-18: LGTM! Explicit line endings improve test clarity.

Adding explicit \r\n to test data makes the line-ending expectations clear, which is particularly important given this PR's focus on redesigning line-reading APIs with stricter line-ending handling.

Also applies to: 28-28, 31-31, 34-34, 41-41, 51-51

@osipxd osipxd force-pushed the osipxd/optimize-read-line branch from 3784489 to 6c65b2d Compare December 15, 2025 17:01
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (3)
ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/sse/DefaultClientSSESession.kt (1)

234-237: SSE parsing still lacks explicit line-length limits (memory/DoS risk).

The migration to readLine(lineEnding = LineEnding.Lenient) preserves the unbounded line-length behavior (internally uses limit = Long.MAX_VALUE). For network-facing SSE parsing, this remains a potential DoS/memory exhaustion vector.

Consider adding a configurable maximum line length (e.g., 64KB default) and using readLineStrict(limit = maxLineLength, lineEnding = LineEnding.Lenient) to fail fast with TooLongLineException.

ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt (1)

742-753: Potential IndexOutOfBoundsException when buffer becomes empty after CR handling.

After transferString(1) at line 747, the buffer may be empty. The loop continues without awaitContent(), and at line 744, buffer[count - 1] with count == 0 causes buffer[-1] access.

This was flagged before and marked as addressed, but the current code still appears vulnerable:

  1. Buffer has only CR (remaining = 1)
  2. discardCrlfOrCr() returns false (default mode, CR not followed by LF)
  3. transferString(1) transfers CR, buffer now empty
  4. Loop continues, count = minOf(limitLeft, 0) = 0
  5. buffer[count - 1]buffer[-1] → crash

Apply this diff to add content await after transferring lone CR:

         if (readBuffer.buffer[count - 1] == CR) {
             transferString(count = count - 1)
             if (readBuffer.discardCrlfOrCr()) return consumed
             transferString(1) // transfer the CR
+            if (consumed < limit && !awaitContent()) break
         } else {
             // No new line separator
             transferString(count)
             if (consumed < limit && !awaitContent()) break
         }
ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOUnixSocketServerTest.kt (1)

71-72: Make the status-line assertion clearer and avoid Boolean?

statusLine?.contains("200 OK") produces a Boolean?, so assertEquals(true, ...) is a bit awkward and hides the difference between EOF and a non-200 status. It would be clearer to assert non-null explicitly and then assert the condition, for example:

-                    val statusLine = input.readLineStrict()
-                    assertEquals(true, statusLine?.contains("200 OK"), "Expected HTTP 200 OK, got: $statusLine")
+                    val statusLine = input.readLineStrict()
+                    kotlin.test.assertNotNull(statusLine, "Expected status line, got EOF")
+                    kotlin.test.assertTrue(statusLine.contains("200 OK"), "Expected HTTP 200 OK, got: $statusLine")

(or use requireNotNull instead of assertNotNull if you prefer a hard precondition failure).

This keeps the intent of the test while making failures easier to interpret.

🧹 Nitpick comments (4)
ktor-server/ktor-server-plugins/ktor-server-body-limit/common/test/io/ktor/server/plugins/bodylimit/RequestBodyLimitTest.kt (1)

149-156: readUTF8Line → readLine migration preserves test intent for applyLimit semantics

The three tests now use readLine() after applyLimit, covering:

  • Line overflow path throwing PayloadTooLargeException (Line 154).
  • Normal in‑limit read returning the full string (Line 163).
  • Empty/closing channel returning null (Line 176), matching the KTOR‑7254 regression scenario.

These usages align with the new line‑reading API while still validating the key behaviors of applyLimit. If CR/LF edge cases are being exercised elsewhere in the suite, this file is in good shape as-is.

Also applies to: 158-165, 169-178

ktor-server/ktor-server-tests/common/test/io/ktor/tests/server/http/ApplicationRequestContentTest.kt (1)

21-23: Explicit kotlin.test imports are consistent with guidelines

Switching from a wildcard to explicit kotlin.test imports keeps only the needed symbols (Test, assertEquals, assertFailsWith) and leaves the io.ktor.* imports as wildcards as preferred. This is consistent with the guidance to reserve star imports for io.ktor.* packages.

As per coding guidelines, this narrowed import set looks good and keeps dependencies clear.

ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt (1)

555-565: Fragile exception message check in deprecated wrapper.

The check cause.message?.startsWith("Unexpected end of stream after reading") is brittle—if throwEndOfStreamException message changes, this silently breaks. Consider extracting a constant or using a custom exception type. However, since this is a deprecated wrapper, this may be acceptable for the transition period.

ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOUnixSocketServerTest.kt (1)

29-33: Simplify temporary Unix socket file deletion

File.createTempFile already creates the file, so the exists() check is redundant here – delete() is safe to call even if the file is already gone. You can simplify this block without changing behavior:

-        val socketFile = File.createTempFile("ktor-unix-socket-test", ".sock")
-        if (socketFile.exists()) {
-            socketFile.delete()
-        }
+        val socketFile = File.createTempFile("ktor-unix-socket-test", ".sock")
+        socketFile.delete()
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3784489 and 6c65b2d.

📒 Files selected for processing (36)
  • ktor-client/ktor-client-apache5/jvm/test/io/ktor/client/engine/apache5/Apache5HttpClientTest.kt (2 hunks)
  • ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/sse/DefaultClientSSESession.kt (3 hunks)
  • ktor-client/ktor-client-core/jvm/src/io/ktor/client/plugins/cache/storage/FileCacheStorage.kt (2 hunks)
  • ktor-client/ktor-client-curl/desktop/src/io/ktor/client/engine/curl/CurlClientEngine.kt (2 hunks)
  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt (3 hunks)
  • ktor-client/ktor-client-okhttp/jvm/test/io/ktor/client/engine/okhttp/OkHttpHttp2Test.kt (2 hunks)
  • ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/HttpTimeoutTest.kt (3 hunks)
  • ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/LoggingMockedTests.kt (1 hunks)
  • ktor-client/ktor-client-tests/jvm/src/io/ktor/client/tests/HttpClientTest.kt (1 hunks)
  • ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/ChunkedTransferEncoding.kt (2 hunks)
  • ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/HttpParser.kt (3 hunks)
  • ktor-http/ktor-http-cio/common/test/io/ktor/tests/http/cio/ResponseParserTest.kt (1 hunks)
  • ktor-http/ktor-http-cio/jvm/test/io/ktor/tests/http/cio/ChunkedTest.kt (6 hunks)
  • ktor-io/api/ktor-io.api (2 hunks)
  • ktor-io/api/ktor-io.klib.api (2 hunks)
  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt (4 hunks)
  • ktor-io/common/src/io/ktor/utils/io/LineEnding.kt (1 hunks)
  • ktor-io/common/src/io/ktor/utils/io/LineEndingMode.kt (1 hunks)
  • ktor-io/common/test/ByteReadChannelOperationsTest.kt (2 hunks)
  • ktor-io/common/test/ReadLineTest.kt (1 hunks)
  • ktor-io/common/test/ReadUtf8LineTest.kt (4 hunks)
  • ktor-io/jvm/test/ByteReadChannelOperationsJvmTest.kt (1 hunks)
  • ktor-network/common/test/io/ktor/network/sockets/tests/TCPSocketTest.kt (4 hunks)
  • ktor-network/common/test/io/ktor/network/sockets/tests/UnixSocketTest.kt (2 hunks)
  • ktor-network/jvm/test/io/ktor/network/sockets/tests/ClientSocketTest.kt (1 hunks)
  • ktor-network/jvm/test/io/ktor/network/sockets/tests/ServerSocketTest.kt (1 hunks)
  • ktor-server/ktor-server-cio/common/test/io/ktor/tests/server/cio/CIOEngineTest.kt (2 hunks)
  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOUnixSocketServerTest.kt (2 hunks)
  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/ServerPipelineTest.kt (2 hunks)
  • ktor-server/ktor-server-netty/jvm/test/io/ktor/tests/server/netty/NettySpecificTest.kt (2 hunks)
  • ktor-server/ktor-server-plugins/ktor-server-body-limit/common/test/io/ktor/server/plugins/bodylimit/RequestBodyLimitTest.kt (5 hunks)
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt (2 hunks)
  • ktor-server/ktor-server-tests/common/test/io/ktor/tests/server/http/ApplicationRequestContentTest.kt (3 hunks)
  • ktor-server/ktor-server-tests/jvm/test/io/ktor/server/plugins/PartialContentTest.kt (3 hunks)
  • ktor-test-dispatcher/common/src/TestCommon.kt (1 hunks)
  • ktor-utils/jvm/test/io/ktor/tests/utils/DeflaterReadChannelTest.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (16)
  • ktor-server/ktor-server-cio/common/test/io/ktor/tests/server/cio/CIOEngineTest.kt
  • ktor-client/ktor-client-curl/desktop/src/io/ktor/client/engine/curl/CurlClientEngine.kt
  • ktor-client/ktor-client-okhttp/jvm/test/io/ktor/client/engine/okhttp/OkHttpHttp2Test.kt
  • ktor-network/common/test/io/ktor/network/sockets/tests/TCPSocketTest.kt
  • ktor-server/ktor-server-tests/jvm/test/io/ktor/server/plugins/PartialContentTest.kt
  • ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/ChunkedTransferEncoding.kt
  • ktor-utils/jvm/test/io/ktor/tests/utils/DeflaterReadChannelTest.kt
  • ktor-io/common/src/io/ktor/utils/io/LineEndingMode.kt
  • ktor-client/ktor-client-tests/jvm/src/io/ktor/client/tests/HttpClientTest.kt
  • ktor-io/common/test/ByteReadChannelOperationsTest.kt
  • ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/HttpTimeoutTest.kt
  • ktor-network/jvm/test/io/ktor/network/sockets/tests/ServerSocketTest.kt
  • ktor-server/ktor-server-netty/jvm/test/io/ktor/tests/server/netty/NettySpecificTest.kt
  • ktor-client/ktor-client-apache5/jvm/test/io/ktor/client/engine/apache5/Apache5HttpClientTest.kt
  • ktor-http/ktor-http-cio/common/test/io/ktor/tests/http/cio/ResponseParserTest.kt
  • ktor-http/ktor-http-cio/jvm/test/io/ktor/tests/http/cio/ChunkedTest.kt
🧰 Additional context used
📓 Path-based instructions (2)
**/*.kt

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.kt: Follow Kotlin official style guide (https://kotlinlang.org/docs/coding-conventions.html)
Use star imports for io.ktor.* packages
Document all public APIs including parameters, return types, and exceptions
Mark internal APIs with @InternalAPI annotation
Run ./gradlew lintKotlin and fix all linting issues before giving control back to the user
Use ./gradlew formatKotlin to automatically fix formatting issues
Run ./gradlew updateLegacyAbi after making ABI changes to update ABI signature files
Binary compatibility is enforced - all public API changes must be tracked in the /api/ directories
Validate ABI with ./gradlew checkLegacyAbi and update with ./gradlew updateLegacyAbi
API changes must be intentional and well-documented
Error handling follows Kotlin conventions with specific Ktor exceptions

Files:

  • ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/HttpParser.kt
  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOUnixSocketServerTest.kt
  • ktor-network/common/test/io/ktor/network/sockets/tests/UnixSocketTest.kt
  • ktor-server/ktor-server-plugins/ktor-server-body-limit/common/test/io/ktor/server/plugins/bodylimit/RequestBodyLimitTest.kt
  • ktor-client/ktor-client-core/jvm/src/io/ktor/client/plugins/cache/storage/FileCacheStorage.kt
  • ktor-test-dispatcher/common/src/TestCommon.kt
  • ktor-server/ktor-server-tests/common/test/io/ktor/tests/server/http/ApplicationRequestContentTest.kt
  • ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/sse/DefaultClientSSESession.kt
  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
  • ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/LoggingMockedTests.kt
  • ktor-network/jvm/test/io/ktor/network/sockets/tests/ClientSocketTest.kt
  • ktor-io/common/test/ReadLineTest.kt
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt
  • ktor-io/common/src/io/ktor/utils/io/LineEnding.kt
  • ktor-io/jvm/test/ByteReadChannelOperationsJvmTest.kt
  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/ServerPipelineTest.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
**/test/**/*.kt

📄 CodeRabbit inference engine (CLAUDE.md)

Follow test naming pattern: DescribeWhatIsBeingTested

Files:

  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOUnixSocketServerTest.kt
  • ktor-network/common/test/io/ktor/network/sockets/tests/UnixSocketTest.kt
  • ktor-server/ktor-server-plugins/ktor-server-body-limit/common/test/io/ktor/server/plugins/bodylimit/RequestBodyLimitTest.kt
  • ktor-server/ktor-server-tests/common/test/io/ktor/tests/server/http/ApplicationRequestContentTest.kt
  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
  • ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/LoggingMockedTests.kt
  • ktor-network/jvm/test/io/ktor/network/sockets/tests/ClientSocketTest.kt
  • ktor-io/common/test/ReadLineTest.kt
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt
  • ktor-io/jvm/test/ByteReadChannelOperationsJvmTest.kt
  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/ServerPipelineTest.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
🧠 Learnings (24)
📓 Common learnings
Learnt from: bjhham
Repo: ktorio/ktor PR: 4916
File: ktor-server/ktor-server-core/api/ktor-server-core.api:151-151
Timestamp: 2025-06-09T07:08:35.085Z
Learning: Breaking changes are acceptable in Ktor codebase when the code hasn't been released yet, as confirmed by bjhham from the development team.
📚 Learning: 2025-05-30T06:45:52.309Z
Learnt from: rururux
Repo: ktorio/ktor PR: 4896
File: ktor-client/ktor-client-core/jvm/test/FileStorageTest.kt:1-12
Timestamp: 2025-05-30T06:45:52.309Z
Learning: In Ktor test files, particularly in the ktor-client/ktor-client-core/jvm/test/ directory, test files follow the convention of not including explicit package declarations. This is consistent across test files like CachingCacheStorageTest.kt and should be maintained for consistency.

Applied to files:

  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOUnixSocketServerTest.kt
  • ktor-network/common/test/io/ktor/network/sockets/tests/UnixSocketTest.kt
  • ktor-server/ktor-server-plugins/ktor-server-body-limit/common/test/io/ktor/server/plugins/bodylimit/RequestBodyLimitTest.kt
  • ktor-client/ktor-client-core/jvm/src/io/ktor/client/plugins/cache/storage/FileCacheStorage.kt
  • ktor-test-dispatcher/common/src/TestCommon.kt
  • ktor-server/ktor-server-tests/common/test/io/ktor/tests/server/http/ApplicationRequestContentTest.kt
  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
  • ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/LoggingMockedTests.kt
  • ktor-network/jvm/test/io/ktor/network/sockets/tests/ClientSocketTest.kt
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt
  • ktor-io/jvm/test/ByteReadChannelOperationsJvmTest.kt
  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/ServerPipelineTest.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
📚 Learning: 2025-11-25T09:38:19.393Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:38:19.393Z
Learning: Applies to **/*.kt : Use star imports for `io.ktor.*` packages

Applied to files:

  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOUnixSocketServerTest.kt
  • ktor-test-dispatcher/common/src/TestCommon.kt
  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
📚 Learning: 2025-05-16T13:11:28.416Z
Learnt from: osipxd
Repo: ktorio/ktor PR: 4860
File: ktor-server/ktor-server-plugins/ktor-server-di/api/ktor-server-di.api:24-26
Timestamp: 2025-05-16T13:11:28.416Z
Learning: Binary incompatible changes (such as constructor signature changes) are acceptable in the ktor-server-di module when the version is not yet released, as confirmed by the development team.

Applied to files:

  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOUnixSocketServerTest.kt
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt
📚 Learning: 2025-11-25T09:38:19.393Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:38:19.393Z
Learning: Applies to **/*.kt : API changes must be intentional and well-documented

Applied to files:

  • ktor-server/ktor-server-plugins/ktor-server-body-limit/common/test/io/ktor/server/plugins/bodylimit/RequestBodyLimitTest.kt
  • ktor-client/ktor-client-core/jvm/src/io/ktor/client/plugins/cache/storage/FileCacheStorage.kt
  • ktor-test-dispatcher/common/src/TestCommon.kt
  • ktor-server/ktor-server-tests/common/test/io/ktor/tests/server/http/ApplicationRequestContentTest.kt
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt
  • ktor-io/jvm/test/ByteReadChannelOperationsJvmTest.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
  • ktor-io/api/ktor-io.api
📚 Learning: 2025-06-23T12:49:56.883Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-06-23T12:49:56.883Z
Learning: Error handling should follow Kotlin conventions and use specific Ktor exceptions.

Applied to files:

  • ktor-server/ktor-server-plugins/ktor-server-body-limit/common/test/io/ktor/server/plugins/bodylimit/RequestBodyLimitTest.kt
  • ktor-server/ktor-server-tests/common/test/io/ktor/tests/server/http/ApplicationRequestContentTest.kt
  • ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/sse/DefaultClientSSESession.kt
  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
📚 Learning: 2025-09-05T12:46:14.074Z
Learnt from: bjhham
Repo: ktorio/ktor PR: 4887
File: ktor-server/ktor-server-jetty-jakarta/jvm/src/io/ktor/server/jetty/jakarta/JettyWebsocketConnection.kt:90-100
Timestamp: 2025-09-05T12:46:14.074Z
Learning: The ByteReadChannel.readAvailable(ByteBuffer) method in Ktor IO automatically calls awaitContent() internally when the read buffer is exhausted. When it returns 0, it has already suspended and waited for data to become available, so adding explicit awaitContent() calls is redundant and incorrect.

Applied to files:

  • ktor-server/ktor-server-plugins/ktor-server-body-limit/common/test/io/ktor/server/plugins/bodylimit/RequestBodyLimitTest.kt
  • ktor-server/ktor-server-tests/common/test/io/ktor/tests/server/http/ApplicationRequestContentTest.kt
  • ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/sse/DefaultClientSSESession.kt
  • ktor-io/api/ktor-io.klib.api
  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
  • ktor-io/jvm/test/ByteReadChannelOperationsJvmTest.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
  • ktor-io/api/ktor-io.api
📚 Learning: 2025-11-25T09:38:19.393Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:38:19.393Z
Learning: Applies to **/*.kt : Binary compatibility is enforced - all public API changes must be tracked in the `/api/` directories

Applied to files:

  • ktor-client/ktor-client-core/jvm/src/io/ktor/client/plugins/cache/storage/FileCacheStorage.kt
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt
  • ktor-io/api/ktor-io.api
📚 Learning: 2025-11-27T10:07:26.686Z
Learnt from: osipxd
Repo: ktorio/ktor PR: 5205
File: ktor-client/ktor-client-curl/desktop/interop/include/curl/multi.h:410-420
Timestamp: 2025-11-27T10:07:26.686Z
Learning: In the ktorio/ktor repository, do not review files under `ktor-client/ktor-client-curl/desktop/interop/include/curl/*` as these are upstream libcurl header files copied from the curl project and not maintained by the Ktor team.

Applied to files:

  • ktor-client/ktor-client-core/jvm/src/io/ktor/client/plugins/cache/storage/FileCacheStorage.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
📚 Learning: 2025-11-25T09:38:19.393Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:38:19.393Z
Learning: Applies to **/test/**/*.kt : Follow test naming pattern: `DescribeWhatIsBeingTested`

Applied to files:

  • ktor-test-dispatcher/common/src/TestCommon.kt
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
📚 Learning: 2025-11-25T09:38:19.393Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:38:19.393Z
Learning: Applies to **/*.kt : Error handling follows Kotlin conventions with specific Ktor exceptions

Applied to files:

  • ktor-server/ktor-server-tests/common/test/io/ktor/tests/server/http/ApplicationRequestContentTest.kt
  • ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/sse/DefaultClientSSESession.kt
  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
📚 Learning: 2025-09-05T12:47:49.016Z
Learnt from: bjhham
Repo: ktorio/ktor PR: 4887
File: ktor-server/ktor-server-jetty-jakarta/jvm/src/io/ktor/server/jetty/jakarta/JettyWebsocketConnection.kt:35-52
Timestamp: 2025-09-05T12:47:49.016Z
Learning: JettyWebsocketConnection in ktor-server-jetty-jakarta implements Closeable interface (has close() method), so connection.use { } is valid Kotlin syntax and will compile correctly.

Applied to files:

  • ktor-server/ktor-server-tests/common/test/io/ktor/tests/server/http/ApplicationRequestContentTest.kt
  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
📚 Learning: 2025-05-30T06:45:52.309Z
Learnt from: rururux
Repo: ktorio/ktor PR: 4896
File: ktor-client/ktor-client-core/jvm/test/FileStorageTest.kt:1-12
Timestamp: 2025-05-30T06:45:52.309Z
Learning: The headersOf() function from io.ktor.http package is available through wildcard imports like `import io.ktor.http.*`, so no explicit import statement is needed when using wildcard imports from that package.

Applied to files:

  • ktor-server/ktor-server-tests/common/test/io/ktor/tests/server/http/ApplicationRequestContentTest.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
📚 Learning: 2025-06-16T17:03:45.729Z
Learnt from: tre3p
Repo: ktorio/ktor PR: 4936
File: ktor-utils/jvm/src/io/ktor/util/ZstdEncoding.kt:28-35
Timestamp: 2025-06-16T17:03:45.729Z
Learning: In the Ktor codebase, encoder implementations consistently use GlobalScope.writer and GlobalScope.reader patterns for compression/decompression operations. This is the established architectural pattern used throughout the codebase for byte channel transformations.

Applied to files:

  • ktor-io/api/ktor-io.klib.api
  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
📚 Learning: 2025-09-30T07:52:14.769Z
Learnt from: yschimke
Repo: ktorio/ktor PR: 4013
File: ktor-client/ktor-client-android/jvm/src/io/ktor/client/engine/android/Android14URLConnectionFactory.kt:24-26
Timestamp: 2025-09-30T07:52:14.769Z
Learning: In the Ktor Android HTTP client engine (ktor-client-android), prefer using `URI.create(urlString).toURL()` over the `URL(urlString)` constructor when opening connections with Android's HttpEngine, as it avoids deprecated APIs and the different exception behavior (IllegalArgumentException vs MalformedURLException) is acceptable.

Applied to files:

  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
📚 Learning: 2025-09-05T12:47:49.016Z
Learnt from: bjhham
Repo: ktorio/ktor PR: 4887
File: ktor-server/ktor-server-jetty-jakarta/jvm/src/io/ktor/server/jetty/jakarta/JettyWebsocketConnection.kt:35-52
Timestamp: 2025-09-05T12:47:49.016Z
Learning: Jetty's AbstractConnection class implements Closeable interface, so any class extending AbstractConnection (like JettyWebsocketConnection) can be used with Kotlin's use { } extension function for resource management.

Applied to files:

  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
  • ktor-io/common/test/ReadUtf8LineTest.kt
📚 Learning: 2025-11-14T14:11:30.292Z
Learnt from: osipxd
Repo: ktorio/ktor PR: 5195
File: ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/JavaHttp2Test.kt:13-15
Timestamp: 2025-11-14T14:11:30.292Z
Learning: In ktor-client-java tests, Java's HttpClient does support h2c (HTTP/2 cleartext) when configured with `protocolVersion = HttpClient.Version.HTTP_2` on Java 11+. The JavaHttp2Test successfully passes with the default useH2c=true setting, connecting to http://localhost:8084.

Applied to files:

  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
📚 Learning: 2025-05-14T18:05:02.321Z
Learnt from: bjhham
Repo: ktorio/ktor PR: 4855
File: ktor-server/ktor-server-plugins/ktor-server-di/api/ktor-server-di.klib.api:334-336
Timestamp: 2025-05-14T18:05:02.321Z
Learning: Breaking changes in constructor parameter order are acceptable for the ktor-server-di module when the code hasn't been released yet, as confirmed by the development team.

Applied to files:

  • ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt
  • ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt
  • ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/ServerPipelineTest.kt
  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
📚 Learning: 2025-06-09T07:08:35.085Z
Learnt from: bjhham
Repo: ktorio/ktor PR: 4916
File: ktor-server/ktor-server-core/api/ktor-server-core.api:151-151
Timestamp: 2025-06-09T07:08:35.085Z
Learning: Breaking changes are acceptable in Ktor codebase when the code hasn't been released yet, as confirmed by bjhham from the development team.

Applied to files:

  • ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/LoggingMockedTests.kt
  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
📚 Learning: 2025-06-16T17:13:12.657Z
Learnt from: tre3p
Repo: ktorio/ktor PR: 4936
File: ktor-utils/api/ktor-utils.api:498-506
Timestamp: 2025-06-16T17:13:12.657Z
Learning: The user tre3p removed ZstdInputStream/ZstdOutputStream from their ZstdEncoder implementation to address blocking I/O concerns in coroutine contexts.

Applied to files:

  • ktor-io/jvm/test/ByteReadChannelOperationsJvmTest.kt
📚 Learning: 2025-06-23T12:49:56.883Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-06-23T12:49:56.883Z
Learning: Prefix commit messages with KTOR-{NUM} (YouTrack issue), and write them in present tense, imperative mood.

Applied to files:

  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
📚 Learning: 2025-11-25T09:38:19.393Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:38:19.393Z
Learning: Prefix commit messages with KTOR-{NUM} (YouTrack issue number)

Applied to files:

  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
📚 Learning: 2025-07-01T10:54:53.751Z
Learnt from: osipxd
Repo: ktorio/ktor PR: 4970
File: build-logic/src/main/kotlin/ktorbuild.kmp.gradle.kts:67-72
Timestamp: 2025-07-01T10:54:53.751Z
Learning: In build-logic/src/main/kotlin/ktorbuild.kmp.gradle.kts, the KT-70915 and KT-77609 workarounds that limit parallelism for KotlinNativeLink and CInteropCommonizerTask should remain unconditional (not gated behind CI flag) as these limits are beneficial for both local and CI builds.

Applied to files:

  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
📚 Learning: 2025-11-25T09:38:19.393Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:38:19.393Z
Learning: Applies to **/*.kt : Document all public APIs including parameters, return types, and exceptions

Applied to files:

  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
🧬 Code graph analysis (4)
ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/CIOUnixSocketServerTest.kt (2)
ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/HttpRequestRetry.kt (1)
  • delay (243-245)
ktor-network/common/src/io/ktor/network/sockets/Builders.kt (1)
  • aSocket (14-14)
ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/sse/DefaultClientSSESession.kt (1)
ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt (1)
  • readLine (577-582)
ktor-io/common/test/ReadLineTest.kt (3)
ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/HttpRequestRetry.kt (1)
  • delay (243-245)
ktor-server/ktor-server-test-suites/jvm/src/io/ktor/server/testing/suites/Utils.kt (1)
  • assertFailsWith (94-104)
ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt (3)
  • readLineTo (595-606)
  • readLineStrictTo (651-665)
  • exhausted (32-36)
ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt (4)
ktor-io/common/src/io/ktor/utils/io/ByteChannel.kt (1)
  • awaitContent (69-80)
ktor-io/common/src/io/ktor/utils/io/CountedByteReadChannel.kt (1)
  • awaitContent (43-53)
ktor-io/jvm/src/io/ktor/utils/io/jvm/javaio/Reading.kt (1)
  • awaitContent (66-87)
ktor-io/common/src/io/ktor/utils/io/core/ByteReadPacket.kt (1)
  • discard (69-75)
🔇 Additional comments (27)
ktor-test-dispatcher/common/src/TestCommon.kt (1)

7-16: Imports are consistent and minimal

Explicit imports correctly cover all used coroutine/time types, with no unused entries and no API/behavior changes. This aligns with the guideline to only use star imports for io.ktor.* packages.

ktor-server/ktor-server-cio/jvm/test/io/ktor/tests/server/cio/ServerPipelineTest.kt (1)

85-87: LGTM! API migration is correct.

The migration from readUTF8Line() to readLineStrict() is correct and aligns with the PR's goal to replace deprecated UTF-8 line readers with the new line-reading API. The test assertions remain unchanged and the new API correctly handles CRLF line endings used in these HTTP responses.

Also applies to: 120-122

ktor-server/ktor-server-plugins/ktor-server-body-limit/common/test/io/ktor/server/plugins/bodylimit/RequestBodyLimitTest.kt (1)

2-2: Header year bump and explicit test imports look good

Updating the copyright year and switching to explicit imports for runTest and kotlin.test assertions while keeping star imports only for io.ktor.* is consistent with the stated style guidelines. No further changes needed here.

Also applies to: 16-20

ktor-client/ktor-client-tests/common/test/io/ktor/client/tests/LoggingMockedTests.kt (1)

326-333: Switch to readLineStrict is appropriate here

Given that this test deliberately writes a single newline‑terminated line ("Hello world!\n") and closes the source channel after the first collected element, using readLineStrict() instead of the old nullable readUTF8Line() is consistent with the new API and avoids propagating a nullable line value through the flow. The surrounding while (!ch.isClosedForRead) { ch.awaitContent(); ... } pattern remains valid under the stricter semantics, since EOF without a newline would rightly surface as an error rather than quietly producing null.

ktor-server/ktor-server-test-host/common/test/TestApplicationTest.kt (1)

523-526: No changes needed—code correctly uses readLineStrict().

The return type is String? and the function returns null (rather than throws) when the channel is empty at the start of a read attempt. EOFException is only thrown if a line is partially read but never terminates with a line break before the channel closes. In this test, the server writes all 3 lines with proper line endings, so the ?: break pattern correctly handles the normal EOF case where no more data is available.

ktor-server/ktor-server-tests/common/test/io/ktor/tests/server/http/ApplicationRequestContentTest.kt (2)

2-2: License header year range update is appropriate

Extending the copyright range to 2025 matches the current development period and has no behavioral impact.


207-207: Using readLine() here aligns the test with the new line-reading API

Replacing readUTF8Line() with readLine() keeps the assertion semantics the same for this case (no line terminator in "bodyContent"), while exercising the new line-reading implementation in ByteReadChannel. This helps validate the new API under DoubleReceive caching.

Please double-check that the new readLine behavior for “EOF without terminator” matches the old readUTF8Line contract (returning the collected line rather than null), since this test depends on that behavior to pass.

ktor-io/jvm/test/ByteReadChannelOperationsJvmTest.kt (2)

6-8: LGTM! Import changes follow Kotlin conventions.

The explicit imports for kotlinx.coroutines and kotlin.test improve clarity while correctly maintaining the star import for io.ktor.utils.io.* as required by the coding guidelines.

Also applies to: 14-15


17-117: Tests for the new line-reading APIs already exist elsewhere. Comprehensive test coverage for readLine, readLineStrict, readLineTo, and readLineStrictTo is maintained in ktor-io/common/test/ReadLineTest.kt, with the deprecated readUTF8Line* APIs tested separately in ktor-io/common/test/ReadUtf8LineTest.kt. The removal of the readUTF8LineTo test from ByteReadChannelOperationsJvmTest.kt is appropriate and does not create a coverage gap.

ktor-client/ktor-client-java/jvm/test/io/ktor/client/engine/java/ResponseConsumerTest.kt (3)

2-3: Header year update is fine

Bumping the copyright range to 2014–2025 and keeping the standard header format looks correct; no further changes needed here.


12-22: Imports are consistent with usage and style

The added explicit imports for coroutines, java.net.http, and kotlin.test match the usages in this file, while io.ktor.* packages remain star-imported as recommended. No issues here.

As per coding guidelines, keeping star imports only for io.ktor.* is appropriate.


58-58: Switch to readLine() aligns with new line-reading API

Using (responseBody.body as ByteReadChannel).readLine() inside runBlocking correctly migrates this test from the deprecated readUTF8Line() to the new API, and the assertion still verifies that the full body ("ktor") is read from the channel. Behavior and intent of the test remain unchanged.

ktor-network/jvm/test/io/ktor/network/sockets/tests/ClientSocketTest.kt (1)

92-92: The migration from readUTF8Line() to readLine() is appropriate. The API documentation confirms that readLine() explicitly treats EOF as an implicit line boundary: "Appends UTF-8 characters until a line break is found or the channel is exhausted. Implicit line break is assumed if the channel ends without a line break." This is exactly the scenario the test verifies—data ending at EOF without an explicit line terminator. The test is valid and the behavior is preserved.

ktor-network/common/test/io/ktor/network/sockets/tests/UnixSocketTest.kt (1)

40-40: LGTM! Test migration to new line-reading API is correct.

The migration from readUTF8Line() to readLineStrict() is appropriate for this test. Both test messages include newline terminators, so the strict variant will correctly read the lines.

Also applies to: 49-49

ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/HttpParser.kt (1)

45-45: LGTM! HTTP parser correctly migrated to new line-reading API.

The migration preserves existing behavior:

  • EOF detection now checks for -1L return value instead of boolean
  • The 8192-byte line limit is maintained
  • Error handling and parsing logic remain intact

Also applies to: 81-81, 122-122

ktor-io/common/src/io/ktor/utils/io/LineEnding.kt (1)

1-26: LGTM! Well-designed public API for line-ending configuration.

The new LineEnding enum is:

  • Clearly documented with usage guidance
  • Follows Kotlin and Ktor conventions
  • Provides sensible defaults (Default mode recommended, Lenient for compatibility)
  • Properly integrated with the feedback system
ktor-io/common/test/ReadLineTest.kt (1)

1-328: Excellent test coverage for new line-reading APIs.

This comprehensive test suite effectively validates:

  • Buffer boundary handling (CR/LF split across buffers)
  • LineEnding modes (Default vs Lenient)
  • EOF semantics (strict throws EOFException, lenient returns partial)
  • Limit enforcement (exact limits, exceeding limits, TooLongLineException)
  • Edge cases (empty channels, position-0 endings, mixed line endings)

The data-driven approach at lines 245-310 provides good coverage of various line-ending combinations.

ktor-io/common/test/ReadUtf8LineTest.kt (2)

14-18: LGTM! Proper approach to maintain legacy API compatibility tests.

The comment clearly explains that these tests verify the deprecated readUTF8LineTo behavior is preserved, while the new ReadLineTest covers the new API surface. The @Suppress("DEPRECATION") annotation is appropriate.


22-93: LGTM! Test naming and coverage improvements.

The migration to backtick-quoted natural language test names improves readability and follows Ktor test conventions. The new test cases for flush scenarios and lines without endings expand edge-case coverage.

ktor-io/api/ktor-io.klib.api (1)

50-59: LGTM! API surface properly expanded with new line-reading capabilities.

The new public API is well-designed:

  • LineEnding enum provides clear line-ending semantics
  • Line-reading functions (readLine, readLineStrict, readLineTo, readLineStrictTo) follow consistent naming
  • Default parameters ensure backward compatibility
  • Binary compatibility is preserved (deprecated APIs at lines 513-515 remain available)

The changes are properly tracked in the ABI signature file as required by Ktor's binary compatibility enforcement.

As per coding guidelines, remember to run:

  • ./gradlew checkLegacyAbi to validate ABI compatibility
  • ./gradlew updateLegacyAbi if needed after review approval

Also applies to: 503-506

ktor-io/api/ktor-io.api (1)

88-95: LGTM! New public API surface is well-structured.

The new LineEnding enum and readLine* family of functions are properly exposed with correct default parameter handling. The API maintains binary compatibility by keeping the deprecated readUTF8Line* functions. As per coding guidelines, public API changes are tracked in the /api/ directory as required.

Also applies to: 319-325

ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt (6)

420-474: Excellent deprecation documentation.

The migration guide is comprehensive, covering both the direct equivalent and the recommended approach. The examples clearly show before/after patterns.


567-606: LGTM! Clean API design.

The readLine and readLineTo functions correctly delegate to the internal implementation with appropriate defaults. The removal of the limit parameter from non-strict variants is intentional and aligns with kotlinx-io conventions.


608-665: LGTM! Strict variants are well-designed.

The require(limit >= 0) validation and clear exception semantics (TooLongLineException, EOFException) are properly documented and implemented.


755-781: LGTM! Post-loop handling is robust.

The distinction between EOF (incomplete line) and TooLongLineException (limit exceeded) is correctly implemented. The check() guard at line 757 provides good defensive programming against implementation bugs.


783-789: LGTM! Helper functions correctly return Nothing.

Both throwTooLongLineException and throwEndOfStreamException have the correct Nothing return type for non-returning functions.


281-281: LGTM! Documentation update.

The reference to Buffer.remaining is accurate for the current API.

@osipxd osipxd force-pushed the osipxd/optimize-read-line branch from 6c65b2d to fd1747c Compare December 15, 2025 17:43
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
ktor-utils/jvm/test/io/ktor/tests/utils/DeflaterReadChannelTest.kt (1)

21-23: Class-level @Ignore looks fine; consider narrowing or adding explicit re‑enable note

Using @Ignore("KTOR-9179 Fix races in DeflaterReadChannelTest") at class level is appropriate to park a flaky/racy suite and the reason is clear. If possible, consider:

  • Restricting the ignore to only the specific flaky test(s), or
  • Adding a short comment/TODO near the annotation about re‑enabling once KTOR‑9179 is resolved, so this doesn’t become a permanent skip.
ktor-io/common/test/ReadLineTest.kt (1)

220-224: Test name is misleading - it tests lenient EOF handling, not limit.

The test is named limit - exact limit at end of stream succeeds in lenient mode, but it calls assertLines without a limit (defaulting to -1L), which means it uses readLineTo (unlimited). The test actually verifies that lenient mode accepts content without a trailing line break, not limit behavior.

Consider renaming to lenient mode - line without trailing line break succeeds or similar.

     @Test
-    fun `limit - exact limit at end of stream succeeds in lenient mode`() = runTest {
+    fun `lenient mode - line without trailing line break succeeds`() = runTest {
         val channel = ByteReadChannel("12345")
         channel.assertLines("12345", lineEnding = LineEnding.Lenient)
     }
ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt (1)

555-565: Fragile exception message check in deprecated wrapper.

Line 562 checks cause.message?.startsWith("Unexpected end of stream after reading") to distinguish between different EOFException scenarios. This is fragile because:

  1. The message text could change (see line 789)
  2. Other EOFException sources might have different messages

Consider using a dedicated exception subclass or a more robust detection mechanism for the deprecation compatibility layer.

One approach is to define a marker in the exception or use a specific exception type for this case:

-    throw EOFException("Unexpected end of stream after reading $consumed characters")
+    // Consider creating a more specific exception or adding a property

Alternatively, document this coupling clearly between line 562 and line 789.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6c65b2d and fd1747c.

📒 Files selected for processing (5)
  • ktor-io/api/ktor-io.api (2 hunks)
  • ktor-io/api/ktor-io.klib.api (2 hunks)
  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt (4 hunks)
  • ktor-io/common/test/ReadLineTest.kt (1 hunks)
  • ktor-utils/jvm/test/io/ktor/tests/utils/DeflaterReadChannelTest.kt (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.kt

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.kt: Follow Kotlin official style guide (https://kotlinlang.org/docs/coding-conventions.html)
Use star imports for io.ktor.* packages
Document all public APIs including parameters, return types, and exceptions
Mark internal APIs with @InternalAPI annotation
Run ./gradlew lintKotlin and fix all linting issues before giving control back to the user
Use ./gradlew formatKotlin to automatically fix formatting issues
Run ./gradlew updateLegacyAbi after making ABI changes to update ABI signature files
Binary compatibility is enforced - all public API changes must be tracked in the /api/ directories
Validate ABI with ./gradlew checkLegacyAbi and update with ./gradlew updateLegacyAbi
API changes must be intentional and well-documented
Error handling follows Kotlin conventions with specific Ktor exceptions

Files:

  • ktor-io/common/test/ReadLineTest.kt
  • ktor-utils/jvm/test/io/ktor/tests/utils/DeflaterReadChannelTest.kt
  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
**/test/**/*.kt

📄 CodeRabbit inference engine (CLAUDE.md)

Follow test naming pattern: DescribeWhatIsBeingTested

Files:

  • ktor-io/common/test/ReadLineTest.kt
  • ktor-utils/jvm/test/io/ktor/tests/utils/DeflaterReadChannelTest.kt
🧠 Learnings (14)
📚 Learning: 2025-11-25T09:38:19.393Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:38:19.393Z
Learning: Applies to **/*.kt : API changes must be intentional and well-documented

Applied to files:

  • ktor-io/api/ktor-io.api
  • ktor-utils/jvm/test/io/ktor/tests/utils/DeflaterReadChannelTest.kt
  • ktor-io/api/ktor-io.klib.api
  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
📚 Learning: 2025-11-25T09:38:19.393Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:38:19.393Z
Learning: Applies to **/*.kt : Binary compatibility is enforced - all public API changes must be tracked in the `/api/` directories

Applied to files:

  • ktor-io/api/ktor-io.api
📚 Learning: 2025-11-25T09:38:19.393Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:38:19.393Z
Learning: Applies to **/*.kt : Document all public APIs including parameters, return types, and exceptions

Applied to files:

  • ktor-io/api/ktor-io.api
  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
📚 Learning: 2025-09-05T12:46:14.074Z
Learnt from: bjhham
Repo: ktorio/ktor PR: 4887
File: ktor-server/ktor-server-jetty-jakarta/jvm/src/io/ktor/server/jetty/jakarta/JettyWebsocketConnection.kt:90-100
Timestamp: 2025-09-05T12:46:14.074Z
Learning: The ByteReadChannel.readAvailable(ByteBuffer) method in Ktor IO automatically calls awaitContent() internally when the read buffer is exhausted. When it returns 0, it has already suspended and waited for data to become available, so adding explicit awaitContent() calls is redundant and incorrect.

Applied to files:

  • ktor-io/api/ktor-io.api
  • ktor-io/api/ktor-io.klib.api
  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
📚 Learning: 2025-05-30T06:45:52.309Z
Learnt from: rururux
Repo: ktorio/ktor PR: 4896
File: ktor-client/ktor-client-core/jvm/test/FileStorageTest.kt:1-12
Timestamp: 2025-05-30T06:45:52.309Z
Learning: In Ktor test files, particularly in the ktor-client/ktor-client-core/jvm/test/ directory, test files follow the convention of not including explicit package declarations. This is consistent across test files like CachingCacheStorageTest.kt and should be maintained for consistency.

Applied to files:

  • ktor-io/common/test/ReadLineTest.kt
  • ktor-utils/jvm/test/io/ktor/tests/utils/DeflaterReadChannelTest.kt
📚 Learning: 2025-11-25T09:38:19.393Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:38:19.393Z
Learning: Applies to **/test/**/*.kt : Follow test naming pattern: `DescribeWhatIsBeingTested`

Applied to files:

  • ktor-utils/jvm/test/io/ktor/tests/utils/DeflaterReadChannelTest.kt
📚 Learning: 2025-06-16T17:03:45.729Z
Learnt from: tre3p
Repo: ktorio/ktor PR: 4936
File: ktor-utils/jvm/src/io/ktor/util/ZstdEncoding.kt:28-35
Timestamp: 2025-06-16T17:03:45.729Z
Learning: In the Ktor codebase, encoder implementations consistently use GlobalScope.writer and GlobalScope.reader patterns for compression/decompression operations. This is the established architectural pattern used throughout the codebase for byte channel transformations.

Applied to files:

  • ktor-utils/jvm/test/io/ktor/tests/utils/DeflaterReadChannelTest.kt
  • ktor-io/api/ktor-io.klib.api
📚 Learning: 2025-06-23T12:49:56.883Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-06-23T12:49:56.883Z
Learning: Prefix commit messages with KTOR-{NUM} (YouTrack issue), and write them in present tense, imperative mood.

Applied to files:

  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
📚 Learning: 2025-06-23T12:49:56.883Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-06-23T12:49:56.883Z
Learning: Error handling should follow Kotlin conventions and use specific Ktor exceptions.

Applied to files:

  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
📚 Learning: 2025-11-25T09:38:19.393Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:38:19.393Z
Learning: Prefix commit messages with KTOR-{NUM} (YouTrack issue number)

Applied to files:

  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
📚 Learning: 2025-05-14T18:05:02.321Z
Learnt from: bjhham
Repo: ktorio/ktor PR: 4855
File: ktor-server/ktor-server-plugins/ktor-server-di/api/ktor-server-di.klib.api:334-336
Timestamp: 2025-05-14T18:05:02.321Z
Learning: Breaking changes in constructor parameter order are acceptable for the ktor-server-di module when the code hasn't been released yet, as confirmed by the development team.

Applied to files:

  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
📚 Learning: 2025-06-09T07:08:35.085Z
Learnt from: bjhham
Repo: ktorio/ktor PR: 4916
File: ktor-server/ktor-server-core/api/ktor-server-core.api:151-151
Timestamp: 2025-06-09T07:08:35.085Z
Learning: Breaking changes are acceptable in Ktor codebase when the code hasn't been released yet, as confirmed by bjhham from the development team.

Applied to files:

  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
📚 Learning: 2025-11-25T09:38:19.393Z
Learnt from: CR
Repo: ktorio/ktor PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:38:19.393Z
Learning: Applies to **/*.kt : Error handling follows Kotlin conventions with specific Ktor exceptions

Applied to files:

  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
📚 Learning: 2025-07-01T10:54:53.751Z
Learnt from: osipxd
Repo: ktorio/ktor PR: 4970
File: build-logic/src/main/kotlin/ktorbuild.kmp.gradle.kts:67-72
Timestamp: 2025-07-01T10:54:53.751Z
Learning: In build-logic/src/main/kotlin/ktorbuild.kmp.gradle.kts, the KT-70915 and KT-77609 workarounds that limit parallelism for KotlinNativeLink and CInteropCommonizerTask should remain unconditional (not gated behind CI flag) as these limits are beneficial for both local and CI builds.

Applied to files:

  • ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt
🧬 Code graph analysis (2)
ktor-io/common/test/ReadLineTest.kt (1)
ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt (3)
  • readLineTo (595-606)
  • readLineStrictTo (651-665)
  • exhausted (32-36)
ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt (4)
ktor-io/common/src/io/ktor/utils/io/ByteChannel.kt (1)
  • awaitContent (69-80)
ktor-io/common/src/io/ktor/utils/io/CountedByteReadChannel.kt (1)
  • awaitContent (43-53)
ktor-io/jvm/src/io/ktor/utils/io/jvm/javaio/Reading.kt (1)
  • awaitContent (66-87)
ktor-io/common/src/io/ktor/utils/io/core/ByteReadPacket.kt (1)
  • discard (69-75)
🔇 Additional comments (13)
ktor-io/common/test/ReadLineTest.kt (2)

1-11: LGTM on test file setup.

The test file correctly follows Ktor conventions by omitting the package declaration. Imports are appropriate and the test framework usage with runTest is correct.


312-327: Helper function handles both strict and non-strict modes correctly.

The assertLines helper elegantly switches between readLineTo and readLineStrictTo based on the limit parameter. The exhausted check at line 326 ensures all expected lines are consumed.

ktor-io/api/ktor-io.klib.api (2)

50-59: New LineEnding enum API is correctly exposed.

The enum with Default and Lenient entries along with standard enum utilities is properly declared in the public API surface.


503-506: New line-reading APIs are properly declared.

The new suspend functions (readLine, readLineStrict, readLineStrictTo, readLineTo) with their LineEnding parameters are correctly added to the public API surface. As per coding guidelines, API changes must be intentional and well-documented - the corresponding KDoc in the implementation provides this documentation.

ktor-io/api/ktor-io.api (2)

88-95: JVM API additions for line-reading are correctly declared.

The new readLine, readLineStrict, readLineStrictTo, and readLineTo methods along with their $default synthetic overloads are properly exposed in the JVM public API. The signatures correctly include LineEnding and Continuation parameters.


319-325: LineEnding enum is correctly exposed in JVM API.

The enum extends java.lang.Enum with Default and Lenient fields, and includes standard Kotlin enum utilities (getEntries, valueOf, values).

ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt (7)

420-474: Excellent deprecation documentation.

The KDoc thoroughly explains the migration path from readUTF8Line to the new APIs, including code examples showing both direct equivalents and recommended alternatives. This helps users understand the behavioral differences between Default and Lenient line ending modes.


742-754: Edge case for empty buffer after CR handling is properly addressed.

Line 753 correctly handles the scenario where the buffer becomes empty after transferring a lone CR. The check readBuffer.remaining == 0L && !awaitContent() ensures we either get more data or exit the loop before the next iteration attempts to access buffer indices.


584-606: New readLineTo API is well-designed.

The function correctly:

  • Defaults to LineEnding.Default for optimal performance
  • Returns the number of characters appended (or -1 for empty channel)
  • Doesn't throw on missing line break at EOF (unlike strict variant)

The KDoc clearly documents the behavior difference from the deprecated readUTF8LineTo.


632-665: New readLineStrictTo API is well-designed with clear exception semantics.

The function:

  • Validates that limit >= 0 (line 657)
  • Throws TooLongLineException if limit exceeded
  • Throws EOFException if channel closes before line break
  • Documents that limit counts characters, not bytes

The KDoc includes the important note that bytes are consumed even on failure.


758-778: Good defensive check and corner case handling.

Line 758-760 includes a check() assertion to catch implementation bugs. Lines 768-778 correctly handle the corner case where the line ending appears immediately after the limit is reached, ensuring such lines are accepted without throwing TooLongLineException.


281-281: Minor KDoc improvement.

The reference to Buffer.remaining was updated to align with API changes. The documentation is clear and accurate.


688-701: The edge case concern is invalid. The loop maintains an invariant that remaining >= 1 when scanForSoleCr is called.

At line 720, remaining is guaranteed to be at least 1 because line 753's guard (readBuffer.remaining == 0L && !awaitContent()) breaks the loop if the buffer is exhausted and no more content is available; otherwise, awaitContent() returning true refills the buffer with at least 1 byte. Therefore, line 695's computation minOf(limitLeft, remaining - 1) always produces a non-negative endIndex (at worst 0 when remaining == 1), and the indexOf(CR, endIndex = endIndex) call is safe. Similarly, the buffer access at line 744 (buffer[count - 1]) is safe because count >= 1 (guaranteed by limitLeft > 0 from the loop condition and remaining >= 1).

transferString(count)
}

if (consumed < limit && readBuffer.remaining == 0L && !awaitContent()) break
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it okay to check readBuffer.remaining == 0L or should I call exhuasted() instead?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like readBuffer.remaining == 0 could return a false positive when using a raw source with SourceByteReadChannel, but this is mitigated by calling awaitContent() afterwards.

@osipxd osipxd requested review from bjhham and e5l December 15, 2025 17:52
transferString(count)
}

if (consumed < limit && readBuffer.remaining == 0L && !awaitContent()) break
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like readBuffer.remaining == 0 could return a false positive when using a raw source with SourceByteReadChannel, but this is mitigated by calling awaitContent() afterwards.

Copy link
Member

@e5l e5l left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

*
* ## Deprecation Notes
*
* This function is deprecated in favor of [readLineStrictTo] and [readLineTo].
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* This function is deprecated in favor of [readLineStrictTo] and [readLineTo].
* This function is deprecated. Use [readLineStrictTo] or [readLineTo] instead.

* It recognizes CR, LF and CRLF as a line delimiter.
* It recognizes CR, LF, and CRLF as a line delimiter.
*
* ## Deprecation Notes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* ## Deprecation Notes
* ## Deprecation

* This function is deprecated in favor of [readLineStrictTo] and [readLineTo].
*
* Use [readLineStrictTo] when:
* - Need to limit line length and throw [TooLongLineException] if the limit is reached
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* - Need to limit line length and throw [TooLongLineException] if the limit is reached
* - You need to limit the line length and throw [TooLongLineException] when the limit is reached.

*
* Use [readLineStrictTo] when:
* - Need to limit line length and throw [TooLongLineException] if the limit is reached
* - Expect an explicit line break at the end of a line. [EOFException] will be thrown
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* - Expect an explicit line break at the end of a line. [EOFException] will be thrown
* - You expect an explicit line break at the end of a line. [EOFException] will be thrown

* if the channel is closed before the line break
*
* Use [readLineTo] when:
* - No need to limit line length
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* - No need to limit line length
* - You do not need to limit line length.

* - No need to limit line length
* - An explicit line break at the end of the channel is not mandatory
*
* Other changes:
Copy link
Contributor

@vnikolova vnikolova Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Other changes:
* Compared to `readUTF8LineTo`, the new functions differ in the following ways:

* - An explicit line break at the end of the channel is not mandatory
*
* Other changes:
* - New functions recognize LF and CRLF as a line delimiter by default. This default comes with better performance.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* - New functions recognize LF and CRLF as a line delimiter by default. This default comes with better performance.
* - They recognize LF and CRLF as line delimiters by default, which provides better performance.

* Other changes:
* - New functions recognize LF and CRLF as a line delimiter by default. This default comes with better performance.
* To keep current behavior and recognize all line delimiters (CR, LF, and CRLF), specify [LineEnding.Lenient].
* - New functions return number of appended characters instead of [Boolean] or `-1` if the channel is empty.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* - New functions return number of appended characters instead of [Boolean] or `-1` if the channel is empty.
* - They return the number of appended characters, or`-1` if the channel is empty, instead of returning [Boolean].

* true
* }
* ```
* However, we recommend to use [LineEnding.Default] if possible and verify if the case with
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion for added clarity and conciseness:
" However, we recommend using the default line ending behavior ([LineEnding.Default]) and verifying unexpected end-of-input explicitly:"

Comment on lines 476 to +515
/**
* Reads a line of UTF-8 characters to the specified [out] buffer.
* It recognizes CR, LF and CRLF as a line delimiter.
* It recognizes CR, LF, and CRLF as a line delimiter.
*
* ## Deprecation Notes
*
* This function is deprecated in favor of [readLineStrictTo] and [readLineTo].
*
* Use [readLineStrictTo] when:
* - Need to limit line length and throw [TooLongLineException] if the limit is reached
* - Expect an explicit line break at the end of a line. [EOFException] will be thrown
* if the channel is closed before the line break
*
* Use [readLineTo] when:
* - No need to limit line length
* - An explicit line break at the end of the channel is not mandatory
*
* Other changes:
* - New functions recognize LF and CRLF as a line delimiter by default. This default comes with better performance.
* To keep current behavior and recognize all line delimiters (CR, LF, and CRLF), specify [LineEnding.Lenient].
* - New functions return number of appended characters instead of [Boolean] or `-1` if the channel is empty.
* - [readLineStrictTo] accepts [Long] instead of [Int] as a limit parameter.
*
* The direct equivalent of `readUTF8LineTo` would be:
* ```
* // Before
* val success = channel.readUTF8LineTo(out, max = 1024)
*
* // After
* val success = try {
* channel.readLineStrictTo(out, limit = 1024, lineEnding = LineEnding.Lenient) >= 0
* } catch (_: EOFException) {
* true
* }
* ```
* However, we recommend to use [LineEnding.Default] if possible and verify if the case with
* the unexpected end of line should actually be ignored. We expect the following code to be correct in most cases:
* ```
* val success = channel.readLineTo(out, limit = 1024) >= 0
* ```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggested some small improvements for added clarity and actionability, but it generally looks good as it is!

* @param out the buffer to append line to (line break characters are not appended)
* @param lineEnding line ending mode. Accepts LF and CRLF by default.
*
* @return number of characters appended to [out], or `-1` if can't read from the channel
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @return number of characters appended to [out], or `-1` if can't read from the channel
* @return number of characters appended to [out], or `-1` if the channel is empty

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants