eclipse-vertx / eclipse-vertx/vert.x

NullPointerException in Http2ClientConnection.metricsEnd() when HTTP/2 stream ends before response headers are received

Open
#6,022 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
Java
Stars
14.7k
Forks
2.1k
Avg merge
2d 7h
Merged PRs (30d)
28

Description

### Version

4.5.21

### Context

When using the Vert.x HTTP client with HTTP/2, decompressionSupported=true, and micrometer metrics enabled, a NullPointerException occurs in Http2ClientConnection.metricsEnd() because
handler.response is null.

Http2ClientConnection.metricsEnd() should null-check handler.response before accessing statusCode(). The stream can reach onEnd before response headers are processed

stack trace:
`Caused by: io.netty.handler.codec.http2.Http2Exception$StreamException: Decompressor error detected while delegating data read on streamId 1999
at io.netty.handler.codec.http2.Http2Exception.streamError(Http2Exception.java:171)
at io.netty.handler.codec.http2.DelegatingDecompressorFrameListener$Http2Decompressor.decompress(DelegatingDecompressorFrameListener.java:424)
at io.netty.handler.codec.http2.DelegatingDecompressorFrameListener.onDataRead(DelegatingDecompressorFrameListener.java:140)
at io.netty.handler.codec.http2.Http2FrameListenerDecorator.onDataRead(Http2FrameListenerDecorator.java:34)
at io.netty.handler.codec.http2.Http2FrameListenerDecorator.onDataRead(Http2FrameListenerDecorator.java:34)
at io.netty.handler.codec.http2.Http2EmptyDataFrameListener.onDataRead(Http2EmptyDataFrameListener.java:49)
at io.netty.handler.codec.http2.DefaultHttp2ConnectionDecoder$FrameReadListener.onDataRead(DefaultHttp2ConnectionDecoder.java:320)
at io.netty.handler.codec.http2.DefaultHttp2FrameReader.readDataFrame(DefaultHttp2FrameReader.java:409)
at io.netty.handler.codec.http2.DefaultHttp2FrameReader.processPayloadState(DefaultHttp2FrameReader.java:244)
at io.netty.handler.codec.http2.DefaultHttp2FrameReader.readFrame(DefaultHttp2FrameReader.java:164)
at io.netty.handler.codec.http2.DefaultHttp2ConnectionDecoder.decodeFrame(DefaultHttp2ConnectionDecoder.java:186)
at io.netty.handler.codec.http2.DecoratingHttp2ConnectionDecoder.decodeFrame(DecoratingHttp2ConnectionDecoder.java:61)
at io.netty.handler.codec.http2.DecoratingHttp2ConnectionDecoder.decodeFrame(DecoratingHttp2ConnectionDecoder.java:61)
at io.netty.handler.codec.http2.Http2ConnectionHandler$FrameDecoder.decode(Http2ConnectionHandler.java:391)
at io.netty.handler.codec.http2.Http2ConnectionHandler.decode(Http2ConnectionHandler.java:451)
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:530)
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:469)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290)
at io.vertx.core.http.impl.VertxHttp2ConnectionHandler.channelRead(VertxHttp2ConnectionHandler.java:405)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:289)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1519)
at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1377)
at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1428)
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:530)
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:469)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1357)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:868)
at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:799)
at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:501)
at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:399)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:998)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
... 1 common frames omitted
Caused by: java.lang.NullPointerException: Cannot invoke "io.vertx.core.spi.observability.HttpResponse.statusCode()" because "handler.response" is null
at io.vertx.micrometer.impl.VertxHttpClientMetrics$Instance$1.responseEnd(VertxHttpClientMetrics.java:128)
at io.vertx.micrometer.impl.VertxHttpClientMetrics$Instance$1.responseEnd(VertxHttpClientMetrics.java:82)
at io.vertx.core.http.impl.Http2ClientConnection.metricsEnd(Http2ClientConnection.java:206)
at io.vertx.core.http.impl.Http2ClientConnection.access$200(Http2ClientConnection.java:39)
at io.vertx.core.http.impl.Http2ClientConnection$Stream.onEnd(Http2ClientConnection.java:315)
at io.vertx.core.http.impl.VertxHttp2Stream.onEnd(VertxHttp2Stream.java:138)
at io.vertx.core.http.impl.Http2ConnectionBase.onDataRead(Http2ConnectionBase.java:322)
at io.netty.handler.codec.http2.DelegatingDecompressorFrameListener$Http2Decompressor$1.channelInactive(DelegatingDecompressorFrameListener.java:378)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:303)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:281)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelInactive(AbstractChannelHandlerContext.java:274)
at io.netty.handler.codec.ByteToMessageDecoder.channelInputClosed(ByteToMessageDecoder.java:412)
at io.netty.handler.codec.ByteToMessageDecoder.channelInactive(ByteToMessageDecoder.java:377)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:303)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:281)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelInactive(AbstractChannelHandlerContext.java:274)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelInactive(DefaultChannelPipeline.java:1352)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:301)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:281)
at io.netty.channel.DefaultChannelPipeline.fireChannelInactive(DefaultChannelPipeline.java:850)
at io.netty.channel.AbstractChannel$AbstractUnsafe$7.run(AbstractChannel.java:811)
at io.netty.channel.embedded.EmbeddedEventLoop.runTasks(EmbeddedEventLoop.java:74)
at io.netty.channel.embedded.EmbeddedChannel.runPendingTasks(EmbeddedChannel.java:814)
at io.netty.channel.embedded.EmbeddedChannel.maybeRunPendingTasks(EmbeddedChannel.java:799)
at io.netty.channel.embedded.EmbeddedChannel.finish(EmbeddedChannel.java:547)
at io.netty.channel.embedded.EmbeddedChannel.finish(EmbeddedChannel.java:522)
at io.netty.handler.codec.http2.DelegatingDecompressorFrameListener$Http2Decompressor.decompress(DelegatingDecompressorFrameListener.java:403)
... 44 common frames omitted`

### Steps to reproduce

1. Configure an HTTP/2 server that sends gzip-compressed responses (Content-Encoding: gzip)
2. Create an HTTP client with:
new HttpClientOptions()
.setProtocolVersion(HttpVersion.HTTP_2)
.setDecompressionSupported(true);
3. Enable micrometer metrics (vertx-micrometer-metrics)
4. Send requests to trigger the timing condition (the issue is intermittent and more likely under high stream multiplexing)

### Do you have a reproducer?

_No response_

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.