I am using the spring WebClient to make two calls in parallel.
One of the call results is passed back as a ResponseEntity, and the other result is inspected and then disregarded. Although the transactions are both successful, I see an IllegalReferenceCountException that occurs before any of the WebClient calls actually get executed.
What I see in my logging is that the container logs the exception, then my two HTTP requests get executed successfully, and one of these responses gets returned to the client.
If the shouldBackfill() function returns false, then I execute one HTTP request and return that response (and the IllegalReferenceCountException does not occur).
I was initially thinking that I should release the reference in the second response that I disregard. If I attempt to call releaseBody() directly on the WebClient response. (See https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/reactive/function/client/ClientResponse.html), this does not help. I assume now that the container is detecting that the WebClient request that I disregarded is in an illegal state, hence the error detection. But what I don't understand is that the actual request occurs AFTER the IllegalReferenceCountException gets logged.
Any ideas here on how to get around this? I am wondering if the exception is actually NOT any kind of leak.
The code looks like this:
fun execute(routeHttpRequest: RouteHttpRequest): Mono<ResponseEntity<String>> =
propertyRepository.getProperty(routeHttpRequest.propertyId.orDefault())
.flatMap {
val status = it.getOrElse { unknownStatus(routeHttpRequest.propertyId.orDefault()) }
val response1 = execute(routeHttpRequest, routingRepository.webClientFor(routeHttpRequest))
if (shouldBackfill(routeHttpRequest, status.type())) {
val response2 =
execute(routeHttpRequest, routingRepository.shadowOrBackfillWebClientFor(routeHttpRequest))
zip(response1, response2).map { response ->
compare(routeHttpRequest, response.t1, response.t2, status.type())
response.t1 // response.t2 is NOT returned here..
}
} else response1
}
// This function returns a wrapper on a spring Webclient that makes an HTTP post.
//
private fun execute(routeHttpRequest: RouteHttpRequest, client: Mono<MyWebClient>) =
client
.flatMap { dataService.execute(routeHttpRequest, it) }
.subscribeOn(Schedulers.elastic()) // TODO: consider a dedicated executor here?
private fun shouldBackfill(routeHttpRequest: RouteHttpRequest, migrationStatus: MigrationStatusType): Boolean {
... this logic returns true when we should execute 2 requests in parallel
}
Here's the exception and partial trace:
io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1
at io.netty.util.internal.ReferenceCountUpdater.toLiveRealRefCnt(ReferenceCountUpdater.java:74)
at io.netty.util.internal.ReferenceCountUpdater.release(ReferenceCountUpdater.java:138)
at io.netty.buffer.AbstractReferenceCountedByteBuf.release(AbstractReferenceCountedByteBuf.java:100)
at io.netty.util.ReferenceCountUtil.release(ReferenceCountUtil.java:88)