-
Notifications
You must be signed in to change notification settings - Fork 35
Description
Hi 👋
We’ve run into a reproducible problem using PolygonRestClient with Ktor. The issue comes from the way the library currently manages its HttpClient.
Problem
In PolygonRestClient, the helper methods are implemented like this:
private inline fun <R> withHttpClient(codeBlock: (client: HttpClient) -> R) =
httpClientProvider.buildClient().use(codeBlock)
internal suspend inline fun <reified T> fetchResult(
urlBuilderBlock: URLBuilder.() -> Unit,
vararg options: PolygonRestOption
): T {
val url = baseUrlBuilder.apply(urlBuilderBlock).build()
return withHttpClient { httpClient ->
httpClient.get(url) {
options.forEach { this.it() }
headers["User-Agent"] = Version.userAgent
}
}.body()
}This combination causes two problems:
withHttpClientwraps the client in.use { … }, so the client is closed immediately after the lambda returns..body()is called after the client has already been closed.
As a result, deserialization frequently fails with:
kotlinx.coroutines.JobCancellationException: Parent job is Completed; job=SupervisorJobImpl{Completed}@...
Why this happens
HttpClient.get()is suspendable. The request pipeline is not finished when the block returns.- Because
.use { … }closes the client right away, the pipeline is cancelled. - Then
.body()tries to deserialize from a closed client → cancellation exception.
Suggested Fix
-
Remove
.use { … }and let the client live across calls.HttpClientis designed to be a long-lived, reusable instance.- Close it once when the application shuts down, not per request.
-
Move
.body()inside thewithHttpClientblock, so the response is fully materialized before the client is closed (if you insist on keeping.use).
For example:
private inline fun <R> withHttpClient(codeBlock: (client: HttpClient) -> R): R =
codeBlock(httpClientProvider.buildClient())
internal suspend inline fun <reified T> fetchResult(
urlBuilderBlock: URLBuilder.() -> Unit,
vararg options: PolygonRestOption
): T {
val url = baseUrlBuilder.apply(urlBuilderBlock).build()
return withHttpClient { httpClient ->
httpClient.get(url) {
options.forEach { opt -> opt(this) }
header(HttpHeaders.UserAgent, Version.userAgent)
}.body()
}
}Impact
This makes all REST calls brittle — in a Ktor server context, almost every call to PolygonRestClient eventually fails with JobCancellationException.
Workarounds
The only workaround today is to bypass PolygonRestClient.fetchResult entirely and re-implement REST calls with your own HttpClient. But this defeats the purpose of using the official SDK.
Environment:
- Ktor 2.3.x
- Kotlin 1.9/2.0
- polygon-io/client-jvm (latest release)
- JDK 17
Would you be open to a PR to fix this? I can draft one if you agree with the proposed change.
Thanks!