Skip to content

Conversation

@e5l
Copy link
Member

@e5l e5l commented Nov 23, 2025

Closes #4908

Summary

  • avoid overriding the dispatcher when using a preconfigured OkHttpClient
  • add a JVM test to ensure the engine keeps the preconfigured dispatcher

Testing

  • ./gradlew :ktor-client-okhttp:jvmTest --tests io.ktor.client.engine.okhttp.OkHttpEngineTest

Codex Task

@e5l e5l requested a review from osipxd November 23, 2025 18:27
@e5l e5l self-assigned this Nov 23, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 23, 2025

Walkthrough

Removed the explicit dispatcher assignment from OkHttp client construction so the builder uses its default dispatcher; added a test that verifies a preconfigured OkHttpClient retains its original Dispatcher instance via identity check.

Changes

Cohort / File(s) Summary
OkHttp client construction
ktor-client/ktor-client-okhttp/jvm/src/io/ktor/client/engine/okhttp/OkHttpEngine.kt
Removed the explicit dispatcher(Dispatcher()) assignment from createOkHttpClient, allowing the OkHttpClient builder to rely on its default dispatcher. No other client-building logic changed.
Dispatcher preservation test
ktor-client/ktor-client-okhttp/jvm/test/io/ktor/client/engine/okhttp/OkHttpEngineTest.kt
Added OkHttpEngineTest with usesPreconfiguredDispatcher() which creates a preconfigured OkHttpClient with a custom Dispatcher, instantiates OkHttpEngine with that client, and uses reflection to assert the engine's cached client preserves the same Dispatcher instance.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

  • Pay attention to:
    • That removal of the explicit dispatcher doesn't alter other client behaviors (timeouts, proxy, interceptors).
    • The reflection-based test targets the correct private cache field and may need updates if internals are refactored.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: respecting preconfigured OkHttp dispatchers instead of overriding them.
Description check ✅ Passed The description covers the issue closure, solution summary, and testing instructions but doesn't follow the template structure with explicit Subsystem, Motivation, and Solution sections.
Linked Issues check ✅ Passed The code changes directly address issue #4908 by removing explicit dispatcher configuration to preserve preconfigured OkHttpClient dispatchers [#4908].
Out of Scope Changes check ✅ Passed All changes are in-scope: OkHttpEngine.kt modification removes dispatcher override, and new test file verifies preconfigured dispatcher preservation.
✨ 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 codex/fix-issue-4908-in-ktor

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.

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 (1)
ktor-client/ktor-client-okhttp/jvm/test/io/ktor/client/engine/okhttp/OkHttpEngineTest.kt (1)

12-28: White‑box test correctly covers the regression; only minor optional nits

The test nicely exercises the preconfigured path and verifies dispatcher identity via the cache, which directly guards against regressions in createOkHttpClient.

If you want to tighten things a bit, one optional tweak would be to give clientCache a more precise type when casting (e.g., MutableMap<HttpTimeoutConfig?, OkHttpClient> with an explicit @Suppress("UNCHECKED_CAST")), so future readers immediately see what key/value types are expected rather than working from the engine source. Otherwise this looks good to me.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f5bd49d and 86bf0e4.

📒 Files selected for processing (2)
  • ktor-client/ktor-client-okhttp/jvm/src/io/ktor/client/engine/okhttp/OkHttpEngine.kt (1 hunks)
  • ktor-client/ktor-client-okhttp/jvm/test/io/ktor/client/engine/okhttp/OkHttpEngineTest.kt (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
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.
📚 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-client/ktor-client-okhttp/jvm/test/io/ktor/client/engine/okhttp/OkHttpEngineTest.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-okhttp/jvm/src/io/ktor/client/engine/okhttp/OkHttpEngine.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-client/ktor-client-okhttp/jvm/src/io/ktor/client/engine/okhttp/OkHttpEngine.kt
🔇 Additional comments (1)
ktor-client/ktor-client-okhttp/jvm/src/io/ktor/client/engine/okhttp/OkHttpEngine.kt (1)

144-154: Respecting preconfigured dispatcher looks correct; please double‑check dispatcher lifecycle expectations

Conditionally skipping builder.dispatcher(Dispatcher()) when config.preconfigured != null correctly preserves the caller’s dispatcher configuration while keeping the previous behavior for the non‑preconfigured path.

One thing to keep in mind: cloned clients built from a preconfigured OkHttpClient will now share that same Dispatcher instance, and the engine’s close logic still calls client.dispatcher.executorService.shutdown() for all cached clients. That means closing the engine will also shut down the executor backing the caller’s dispatcher if it is shared elsewhere. Please confirm this lifecycle coupling is acceptable for the intended use cases of preconfigured, or document it if that’s the contract you want.

@e5l e5l enabled auto-merge (squash) November 24, 2025 07:16

builder.dispatcher(Dispatcher())
if (config.preconfigured == null) {
builder.dispatcher(Dispatcher())
Copy link
Member

Choose a reason for hiding this comment

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

Probably we should just drop this line as OkHttp already creates Dispatcher by default:

internal var dispatcher: Dispatcher = Dispatcher()

Choose a reason for hiding this comment

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

Probably we should just drop this line as OkHttp already creates Dispatcher by default:

internal var dispatcher: Dispatcher = Dispatcher()

Yes, because even when copying okHttpClientPrototype, Dispatcher should be shared, as stated in the docs:

OkHttp performs best when you create a single OkHttpClient instance and reuse it for all of your HTTP calls. This is because each client holds its own connection pool and thread pools. Reusing connections and threads reduces latency and saves memory. Conversely, creating a client for each request wastes resources on idle pools.

The above should apply to prototype client also, new Dispatcher should NEVER be set.

@gajicm93
Copy link

gajicm93 commented Nov 25, 2025

It's important to understand that "newBuilder()" method creates new OkHttpClient instance, but reuses original Dispatcher and ConnectionPool. (which is expected and recommended in the docs).

That is why it's important to remove setting new Dispatcher completely, even in the "prototype" instance case, to achieve that all Ktor OkHttpEngine instances reuse the same Dispatcher and ConnectionPool.

EDIT: If somebody doesn't want to share Dispatcher and ConnectionPool instances across OkHttpEngine instances, that can be achieved by explicitly setting DIFFERENT PRECONFIGURED INSTANCES when creating Engine instances, but that should not be the default behaviour.

TLDR: You should just make a "sane default", but leave all the options open to the developer, not force any of your decisions on us.

@e5l e5l requested a review from osipxd December 16, 2025 07:38
Copy link
Member

@osipxd osipxd left a comment

Choose a reason for hiding this comment

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

👍

@osipxd osipxd force-pushed the codex/fix-issue-4908-in-ktor branch from e08f89f to 966c5de Compare December 18, 2025 09:58
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 (2)
ktor-client/ktor-client-okhttp/jvm/test/io/ktor/client/engine/okhttp/OkHttpEngineTest.kt (2)

14-21: Consider adding resource cleanup.

The test creates an OkHttpEngine but doesn't close it. If OkHttpEngine implements Closeable (which engines typically do), consider wrapping the engine usage in a use block or adding explicit cleanup to prevent resource leaks in the test.

🔎 View suggested fix
     @Test
     fun usesPreconfiguredDispatcher() {
         val dispatcher = Dispatcher()
         val preconfiguredClient = OkHttpClient.Builder()
             .dispatcher(dispatcher)
             .build()
 
-        val engine = OkHttpEngine(OkHttpConfig().apply { preconfigured = preconfiguredClient })
-        val cacheField = engine.javaClass.getDeclaredField("clientCache").apply { isAccessible = true }
-        val clientCache = cacheField.get(engine) as Map<*, *>
+        OkHttpEngine(OkHttpConfig().apply { preconfigured = preconfiguredClient }).use { engine ->
+            val cacheField = engine.javaClass.getDeclaredField("clientCache").apply { isAccessible = true }
+            val clientCache = cacheField.get(engine) as Map<*, *>
 
-        val client = clientCache[null] as OkHttpClient
+            val client = clientCache[null] as OkHttpClient
 
-        assertSame(dispatcher, client.dispatcher)
+            assertSame(dispatcher, client.dispatcher)
+        }
     }

22-28: Test correctly verifies dispatcher preservation.

The reflection-based approach is appropriate for verifying internal behavior when no public API exists. The use of assertSame correctly validates instance identity rather than equality, which is exactly what's needed to confirm the preconfigured dispatcher is reused rather than replaced.

The test directly addresses the PR objective and validates the fix for issue #4908.

Optional: Consider adding documentation.

While the reflection approach is justified, a brief comment explaining why reflection is necessary (no public API to verify dispatcher preservation) and what the null key represents in the clientCache could help future maintainers understand the test's assumptions.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a9117b7 and 966c5de.

📒 Files selected for processing (2)
  • ktor-client/ktor-client-okhttp/jvm/src/io/ktor/client/engine/okhttp/OkHttpEngine.kt (0 hunks)
  • ktor-client/ktor-client-okhttp/jvm/test/io/ktor/client/engine/okhttp/OkHttpEngineTest.kt (1 hunks)
💤 Files with no reviewable changes (1)
  • ktor-client/ktor-client-okhttp/jvm/src/io/ktor/client/engine/okhttp/OkHttpEngine.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-client/ktor-client-okhttp/jvm/test/io/ktor/client/engine/okhttp/OkHttpEngineTest.kt
**/test/**/*.kt

📄 CodeRabbit inference engine (CLAUDE.md)

Follow test naming pattern: DescribeWhatIsBeingTested

Files:

  • ktor-client/ktor-client-okhttp/jvm/test/io/ktor/client/engine/okhttp/OkHttpEngineTest.kt
🧠 Learnings (2)
📓 Common learnings
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.
📚 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-client/ktor-client-okhttp/jvm/test/io/ktor/client/engine/okhttp/OkHttpEngineTest.kt
🔇 Additional comments (2)
ktor-client/ktor-client-okhttp/jvm/test/io/ktor/client/engine/okhttp/OkHttpEngineTest.kt (2)

1-3: LGTM!

Standard Apache 2.0 license header is correctly formatted.


5-10: LGTM!

Package declaration and imports are appropriate for the test.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Preconfigured OkHttp dispatcher ignored

4 participants