Skip to content

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

@wbabyte

Description

@wbabyte

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions