Skip to content

Fix HttpTimeout not respecting test dispatchers in runTest - #5512

Merged
Bruce Hamilton (bjhham) merged 2 commits into
ktorio:mainfrom
fru1tworld:fix-4720-timeout-test-dispatcher
Apr 17, 2026
Merged

Fix HttpTimeout not respecting test dispatchers in runTest#5512
Bruce Hamilton (bjhham) merged 2 commits into
ktorio:mainfrom
fru1tworld:fix-4720-timeout-test-dispatcher

Conversation

@fru1tworld

Copy link
Copy Markdown
Contributor

Subsystem
Client

Motivation
#4720

Solution
Capture the caller's coroutine context before launching the timeout and use it when creating the CoroutineScope for applyRequestTimeout, so that test dispatchers (virtual time) are respected.

@fru1tworld
fru1tworld (fru1tworld) marked this pull request as draft April 7, 2026 07:54
@coderabbitai

coderabbitai Bot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5df619c0-c456-4991-8300-04e82bae6551

📥 Commits

Reviewing files that changed from the base of the PR and between 1ae285b and 51e127b.

📒 Files selected for processing (1)
  • ktor-client/ktor-client-core/common/test/HttpTimeoutVirtualTimeTest.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • ktor-client/ktor-client-core/common/test/HttpTimeoutVirtualTimeTest.kt

📝 Walkthrough

Walkthrough

The PR captures the current coroutine context at the start of HttpTimeout's send handler and applies per-request timeouts inside a new CoroutineScope created from that context. It also adds a test verifying request timeout behavior with virtual time in runTest.

Changes

Cohort / File(s) Summary
HttpTimeout Implementation
ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/HttpTimeout.kt
Capture currentCoroutineContext() at the start of the on(Send) handler; when a per-request timeout applies, create CoroutineScope(callerContext) and invoke applyRequestTimeout(...) in that scope rather than directly in the handler's context.
Timeout Test Coverage
ktor-client/ktor-client-core/common/test/HttpTimeoutVirtualTimeTest.kt
Add HttpTimeoutVirtualTimeTest with a runTest that uses MockEngine (handler calls awaitCancellation()), installs HttpTimeout with requestTimeoutMillis = 60_000, and asserts a HttpRequestTimeoutException under virtual time.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Suggested reviewers

  • bjhham
  • osipxd
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main fix: addressing HttpTimeout not respecting test dispatchers in runTest contexts.
Description check ✅ Passed The description follows the template structure with Subsystem, Motivation (linked issue), and Solution sections clearly filled out.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@fru1tworld
fru1tworld (fru1tworld) force-pushed the fix-4720-timeout-test-dispatcher branch from 94540ad to 7a37528 Compare April 7, 2026 07:58

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
ktor-client/ktor-client-mock/common/test/HttpTimeoutVirtualTimeTest.kt (1)

19-36: Consider using use {} for guaranteed client cleanup.

If an unexpected exception occurs (e.g., a different exception type than HttpRequestTimeoutException), client.close() will be skipped. Using the use {} pattern ensures cleanup regardless of test outcome.

♻️ Suggested refactor
     `@Test`
     fun `request timeout fires using virtual time in runTest`() = runTest(timeout = 5.seconds) {
-        val client = HttpClient(MockEngine) {
+        HttpClient(MockEngine) {
             engine {
                 addHandler {
                     awaitCancellation()
                 }
             }
             install(HttpTimeout) {
                 requestTimeoutMillis = 60_000
             }
-        }
-
-        assertFailsWith<HttpRequestTimeoutException> {
-            client.get("http://localhost/test")
+        }.use { client ->
+            assertFailsWith<HttpRequestTimeoutException> {
+                client.get("http://localhost/test")
+            }
         }
-
-        client.close()
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ktor-client/ktor-client-mock/common/test/HttpTimeoutVirtualTimeTest.kt`
around lines 19 - 36, The test creates an HttpClient (HttpClient(MockEngine) {
... }) and calls client.close() manually, which can be skipped if an unexpected
exception is thrown; change this to use the Kotlin use { } pattern so the
HttpClient is always closed — locate the test function request timeout fires
using virtual time in runTest and wrap the HttpClient instantiation (the
HttpClient(MockEngine) block that defines engine { addHandler {
awaitCancellation() } } and installs HttpTimeout) with use { client -> ... } and
move the client.get call and assertion inside the use block so cleanup is
guaranteed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@ktor-client/ktor-client-mock/common/test/HttpTimeoutVirtualTimeTest.kt`:
- Around line 19-36: The test creates an HttpClient (HttpClient(MockEngine) {
... }) and calls client.close() manually, which can be skipped if an unexpected
exception is thrown; change this to use the Kotlin use { } pattern so the
HttpClient is always closed — locate the test function request timeout fires
using virtual time in runTest and wrap the HttpClient instantiation (the
HttpClient(MockEngine) block that defines engine { addHandler {
awaitCancellation() } } and installs HttpTimeout) with use { client -> ... } and
move the client.get call and assertion inside the use block so cleanup is
guaranteed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b0444573-89cb-4159-9a8d-2fd143e46eb8

📥 Commits

Reviewing files that changed from the base of the PR and between 9e2a691 and 94540ad.

📒 Files selected for processing (2)
  • ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/HttpTimeout.kt
  • ktor-client/ktor-client-mock/common/test/HttpTimeoutVirtualTimeTest.kt

@fru1tworld
fru1tworld (fru1tworld) force-pushed the fix-4720-timeout-test-dispatcher branch from 7a37528 to 1ae285b Compare April 7, 2026 08:02
@fru1tworld
fru1tworld (fru1tworld) marked this pull request as ready for review April 7, 2026 08:14

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
ktor-client/ktor-client-core/common/test/HttpTimeoutVirtualTimeTest.kt (1)

19-35: Make client cleanup unconditional.

If the assertion path changes or fails unexpectedly, client.close() may be skipped. Wrap usage in try/finally to guarantee cleanup.

♻️ Suggested refactor
         val client = HttpClient(MockEngine) {
             engine {
                 addHandler {
                     awaitCancellation()
                 }
             }
             install(HttpTimeout) {
                 requestTimeoutMillis = 60_000
             }
         }

-        assertFailsWith<HttpRequestTimeoutException> {
-            client.get("http://localhost/test")
-        }
-
-        client.close()
+        try {
+            assertFailsWith<HttpRequestTimeoutException> {
+                client.get("http://localhost/test")
+            }
+        } finally {
+            client.close()
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ktor-client/ktor-client-core/common/test/HttpTimeoutVirtualTimeTest.kt`
around lines 19 - 35, The test instantiates HttpClient(MockEngine) and asserts a
timeout but currently calls client.close() after the assertion which can be
skipped on failure; wrap the client usage in a try/finally so that the
HttpClient created in the HttpClient(MockEngine) block is always closed in the
finally block (ensure the awaitCancellation handler, install(HttpTimeout)
configuration and the client.get("http://localhost/test") call remain the same,
only move client.close() into finally).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ktor-client/ktor-client-core/common/test/HttpTimeoutVirtualTimeTest.kt`:
- Line 18: Rename the test function
testRequestTimeoutRespectsVirtualTimeInRunTest to use a backticked, descriptive
test name per guidelines; change the declaration of fun
testRequestTimeoutRespectsVirtualTimeInRunTest() = runTest(timeout = 5.seconds)
to a backtick-named Kotlin test such as fun `request timeout respects virtual
time in runTest`() = runTest(timeout = 5.seconds) so the test file follows the
`/test/**/*.kt` naming convention and remains runnable.

---

Nitpick comments:
In `@ktor-client/ktor-client-core/common/test/HttpTimeoutVirtualTimeTest.kt`:
- Around line 19-35: The test instantiates HttpClient(MockEngine) and asserts a
timeout but currently calls client.close() after the assertion which can be
skipped on failure; wrap the client usage in a try/finally so that the
HttpClient created in the HttpClient(MockEngine) block is always closed in the
finally block (ensure the awaitCancellation handler, install(HttpTimeout)
configuration and the client.get("http://localhost/test") call remain the same,
only move client.close() into finally).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0357fb09-ced9-43d5-acd9-c5bf9826187f

📥 Commits

Reviewing files that changed from the base of the PR and between 94540ad and 1ae285b.

📒 Files selected for processing (2)
  • ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/HttpTimeout.kt
  • ktor-client/ktor-client-core/common/test/HttpTimeoutVirtualTimeTest.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/HttpTimeout.kt

Comment thread ktor-client/ktor-client-core/common/test/HttpTimeoutVirtualTimeTest.kt Outdated

@bjhham Bruce Hamilton (bjhham) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you for the fix 🙏

@bjhham
Bruce Hamilton (bjhham) enabled auto-merge (squash) April 17, 2026 10:34
@bjhham
Bruce Hamilton (bjhham) merged commit 8b690f4 into ktorio:main Apr 17, 2026
17 of 19 checks passed
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.

2 participants