Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Http2ClientConnection attempts to write to a null stream when write queued #5342

Open
zekronium opened this issue Oct 2, 2024 · 0 comments
Labels

Comments

@zekronium
Copy link
Contributor

zekronium commented Oct 2, 2024

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.

try {
createStream(request, headers);
} catch (Http2Exception ex) {
if (handler != null) {
handler.handle(context.failedFuture(ex));
}
handleException(ex);
return;
}
if (buf != null) {
doWriteHeaders(headers, false, false, null);
doWriteData(buf, e, handler);
} else {
doWriteHeaders(headers, e, true, handler);
}

private void createStream(HttpRequestHead head, Http2Headers headers) throws Http2Exception {
int id = this.conn.handler.encoder().connection().local().lastStreamCreated();
if (id == 0) {
id = 1;
} else {
id += 2;
}
head.id = id;
head.remoteAddress = conn.remoteAddress();
Http2Stream stream = this.conn.handler.encoder().connection().local().createStream(id, false);
init(stream);
if (conn.metrics != null) {
metric = conn.metrics.requestBegin(headers.path().toString(), head);
}
VertxTracer tracer = context.tracer();
if (tracer != null) {
BiConsumer<String, String> headers_ = (key, val) -> new Http2HeadersAdaptor(headers).add(key, val);
String operation = head.traceOperation;
if (operation == null) {
operation = headers.method().toString();
}
trace = tracer.sendRequest(context, SpanKind.RPC, conn.client.options().getTracingPolicy(), head, operation, headers_, HttpUtils.CLIENT_HTTP_REQUEST_TAG_EXTRACTOR);
}
}

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

@zekronium zekronium added the bug label Oct 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant