-
Notifications
You must be signed in to change notification settings - Fork 38.9k
Open
Labels
in: testIssues in the test moduleIssues in the test modulestatus: waiting-for-triageAn issue we've not yet triaged or decided onAn issue we've not yet triaged or decided on
Description
At the moment, I find no equivalent of WebClient's toBodilessEntity within WebTestClient.
Use-case: Simply inspect that response is returned when its content cannot be inspected or it makes no sense for it to be inspected.
I have the following workaround which requires a lot of boilerplate
var res = client.mutate()
.responseTimeout(Duration.ofSeconds(10))
.build()
.get()
.uri("download")
.exchange()
.expectStatus()
.isOk();
res.returnResult(DataBuffer.class).getResponseBody().map(DataBufferUtils::release).blockLast();If I use any other variant, Netty Leak Detector will signal buffer leaks. One such example is as follows
var body = client.mutate()
.responseTimeout(Duration.ofSeconds(10))
.build()
.get()
.uri("download")
.exchange()
.expectStatus()
.isOk()
.returnResult()
.getResponseBodyContent();
assertThat(body).isNotEmpty();Example above results in netty buffer leaks
2025-12-02T16:32:53.192+01:00 ERROR 11676 --- [demo] [flux-http-nio-3] io.netty.util.ResourceLeakDetector : LEAK: ByteBuf.release() was not called before it's garbage-collected. See https://netty.io/wiki/reference-counted-objects.html for more information.
Recent access records:
#1:
io.netty.handler.codec.http.DefaultHttpContent.release(DefaultHttpContent.java:92)
io.netty.util.ReferenceCountUtil.release(ReferenceCountUtil.java:90)
io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:93)
io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:356)
io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:434)
io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346)
io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318)
io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:249)
io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:354)
io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1429)
io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918)
io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:168)
io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.handle(AbstractNioChannel.java:445)
io.netty.channel.nio.NioIoHandler$DefaultNioRegistration.handle(NioIoHandler.java:388)
io.netty.channel.nio.NioIoHandler.processSelectedKey(NioIoHandler.java:596)
io.netty.channel.nio.NioIoHandler.processSelectedKeysPlain(NioIoHandler.java:541)
io.netty.channel.nio.NioIoHandler.processSelectedKeys(NioIoHandler.java:514)
io.netty.channel.nio.NioIoHandler.run(NioIoHandler.java:484)
io.netty.channel.SingleThreadIoEventLoop.runIo(SingleThreadIoEventLoop.java:225)
io.netty.channel.SingleThreadIoEventLoop.run(SingleThreadIoEventLoop.java:196)
io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:1193)
io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
java.base/java.lang.Thread.run(Thread.java:1474)
#2:
Hint: 'reactor.right.reactiveBridge' will handle the message from this point.
io.netty.handler.codec.http.DefaultHttpContent.touch(DefaultHttpContent.java:86)
io.netty.handler.codec.http.DefaultHttpContent.touch(DefaultHttpContent.java:25)
io.netty.channel.DefaultChannelPipeline.touch(DefaultChannelPipeline.java:115)
io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:343)
io.netty.handler.codec.http.HttpContentDecoder.decode(HttpContentDecoder.java:170)
io.netty.handler.codec.http.HttpContentDecoder.decode(HttpContentDecoder.java:48)
io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:91)
io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:356)
io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:434)
io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346)
io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:333)
io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:455)
io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290)
io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:249)
io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:354)
io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1429)
io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918)
io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:168)
io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.handle(AbstractNioChannel.java:445)
io.netty.channel.nio.NioIoHandler$DefaultNioRegistration.handle(NioIoHandler.java:388)
io.netty.channel.nio.NioIoHandler.processSelectedKey(NioIoHandler.java:596)
io.netty.channel.nio.NioIoHandler.processSelectedKeysPlain(NioIoHandler.java:541)
io.netty.channel.nio.NioIoHandler.processSelectedKeys(NioIoHandler.java:514)
io.netty.channel.nio.NioIoHandler.run(NioIoHandler.java:484)
io.netty.channel.SingleThreadIoEventLoop.runIo(SingleThreadIoEventLoop.java:225)
io.netty.channel.SingleThreadIoEventLoop.run(SingleThreadIoEventLoop.java:196)
io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:1193)
io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
java.base/java.lang.Thread.run(Thread.java:1474)
#3:
org.springframework.core.io.buffer.NettyDataBufferFactory.wrap(NettyDataBufferFactory.java:94)
org.springframework.http.client.reactive.ReactorClientHttpResponse.lambda$getBody$1(ReactorClientHttpResponse.java:110)
reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:106)
reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:203)
reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:122)
reactor.netty.channel.FluxReceive.onInboundNext(FluxReceive.java:383)
reactor.netty.channel.ChannelOperations.onInboundNext(ChannelOperations.java:445)
reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:946)
reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:115)
io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:356)
io.netty.handler.codec.http.HttpContentDecoder.decode(HttpContentDecoder.java:170)
io.netty.handler.codec.http.HttpContentDecoder.decode(HttpContentDecoder.java:48)
io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:91)
io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:356)
io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:434)
io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346)
io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:333)
io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:455)
io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290)
io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:249)
io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:354)
io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1429)
io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918)
io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:168)
io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.handle(AbstractNioChannel.java:445)
io.netty.channel.nio.NioIoHandler$DefaultNioRegistration.handle(NioIoHandler.java:388)
io.netty.channel.nio.NioIoHandler.processSelectedKey(NioIoHandler.java:596)
io.netty.channel.nio.NioIoHandler.processSelectedKeysPlain(NioIoHandler.java:541)
io.netty.channel.nio.NioIoHandler.processSelectedKeys(NioIoHandler.java:514)
io.netty.channel.nio.NioIoHandler.run(NioIoHandler.java:484)
io.netty.channel.SingleThreadIoEventLoop.runIo(SingleThreadIoEventLoop.java:225)
io.netty.channel.SingleThreadIoEventLoop.run(SingleThreadIoEventLoop.java:196)
io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:1193)
io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
java.base/java.lang.Thread.run(Thread.java:1474)
#4:
io.netty.buffer.AdvancedLeakAwareByteBuf.skipBytes(AdvancedLeakAwareByteBuf.java:539)
io.netty.handler.codec.http.HttpObjectDecoder.decode(HttpObjectDecoder.java:518)
io.netty.handler.codec.http.HttpClientCodec$Decoder.decode(HttpClientCodec.java:320)
io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:530)
io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:469)
io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290)
io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:249)
io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:354)
io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1429)
io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918)
io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:168)
io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.handle(AbstractNioChannel.java:445)
io.netty.channel.nio.NioIoHandler$DefaultNioRegistration.handle(NioIoHandler.java:388)
io.netty.channel.nio.NioIoHandler.processSelectedKey(NioIoHandler.java:596)
io.netty.channel.nio.NioIoHandler.processSelectedKeysPlain(NioIoHandler.java:541)
io.netty.channel.nio.NioIoHandler.processSelectedKeys(NioIoHandler.java:514)
io.netty.channel.nio.NioIoHandler.run(NioIoHandler.java:484)
io.netty.channel.SingleThreadIoEventLoop.runIo(SingleThreadIoEventLoop.java:225)
io.netty.channel.SingleThreadIoEventLoop.run(SingleThreadIoEventLoop.java:196)
io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:1193)
io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
java.base/java.lang.Thread.run(Thread.java:1474)
Full example can be found at https://github.com/krezovic/spring-issues/tree/webtestclient-body-leak
Run with ./mvnw clean verify
Metadata
Metadata
Assignees
Labels
in: testIssues in the test moduleIssues in the test modulestatus: waiting-for-triageAn issue we've not yet triaged or decided onAn issue we've not yet triaged or decided on