Http2ClientConnection attempts to write to a null stream when write queued
Version
4.5.9 and above (this part of the vertx code has not been changed for a while now so likely is in older versions too)
Context
Using the Http2Client (via WebClient), the createStream fails due to the connection being closed before stream is created, thus init(stream) is never called. In createStream the call to create the child stream Http2Stream stream = this.conn.handler.encoder().connection().local().createStream(id, false); fails with the connection being closed error.
https://github.com/eclipse-vertx/vert.x/blob/00fa55682875b7e380ad6328708581ed25d96a9f/src/main/java/io/vertx/core/http/impl/Http2ClientConnection.java#L588-L602
https://github.com/eclipse-vertx/vert.x/blob/00fa55682875b7e380ad6328708581ed25d96a9f/src/main/java/io/vertx/core/http/impl/Http2ClientConnection.java#L605-L628
where init stream is set to be used by the VertxHttp2Stream
void init(Http2Stream stream) {
synchronized (this) {
this.stream = stream;
this.writable = this.conn.handler.encoder().flowController().isWritable(stream);
}
stream.setProperty(conn.streamKey, this);
}
At first glance, everything is ok, exception is handled, handler is called, yet while using virtual threads, which always causes writes to queue due to the non even-loop condition !eventLoop.inEventLoop(), the stream appears to try writing to a null stream.
void writeData(Http2Stream stream, ByteBuf chunk, boolean end, FutureListener<Void> listener) {
ChannelPromise promise = listener == null ? chctx.voidPromise() : chctx.newPromise().addListener(listener);
encoder().writeData(chctx, stream.id(), chunk, 0, end, promise); //stream is null here so stream.id() throws NPE
Http2RemoteFlowController controller = encoder().flowController();
if (!controller.isWritable(stream) || end) {
try {
encoder().flowController().writePendingBytes();
} catch (Http2Exception e) {
onError(chctx, true, e);
}
}
checkFlush();
}
[WARN]|AbstractEventExecutor| > A task raised an exception. Task: io.vertx.core.http.impl.VertxHttp2Stream$$Lambda/0x0000000800546148@771dbe57
java.lang.NullPointerException: Cannot invoke "io.netty.handler.codec.http2.Http2Stream.id()" because "stream" is null
at io.vertx.core.http.impl.VertxHttp2ConnectionHandler.writeData(VertxHttp2ConnectionHandler.java:251)
at io.vertx.core.http.impl.VertxHttp2Stream.doWriteData(VertxHttp2Stream.java:252)
at io.vertx.core.http.impl.Http2ClientConnection$Stream.doWriteData(Http2ClientConnection.java:297)
at io.vertx.core.http.impl.VertxHttp2Stream.lambda$writeData$7(VertxHttp2Stream.java:217)
at io.vertx.core.http.impl.VertxHttp2Stream.lambda$queueForWrite$8(VertxHttp2Stream.java:234)
at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:173) ~[netty-common-4.1.111.Final.jar:4.1.111.Final]
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:166) ~[netty-common-4.1.111.Final.jar:4.1.111.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:469) ~[netty-common-4.1.111.Final.jar:4.1.111.Final]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:566) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:994) ~[netty-common-4.1.111.Final.jar:4.1.111.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.111.Final.jar:4.1.111.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.111.Final.jar:4.1.111.Final]
at java.base/java.lang.Thread.run(Thread.java:1583) [?:?]
Do you have a reproducer?
Simulate connection closure while setting up the connection. Use virtual threads.
Extra
Windows Server, Temurin JDK 21.0.3