From 0b250d42389d41076565f20cb44ae163a3daed01 Mon Sep 17 00:00:00 2001 From: Harshal Parekh Date: Wed, 8 Jul 2026 18:10:10 -0500 Subject: [PATCH 1/4] feat: add per-connection attachment storage to HttpConnectionInternal --- .../core/http/impl/http1/Http1Connection.java | 3 +- .../impl/http2/codec/Http2ConnectionImpl.java | 3 +- .../multiplex/Http2MultiplexConnection.java | 3 +- .../core/http/impl/http3/Http3Connection.java | 20 +++++++++- .../internal/http/HttpConnectionInternal.java | 39 +++++++++++++++++++ .../vertx/core/net/impl/ConnectionBase.java | 23 +++++++++++ 6 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 vertx-core/src/main/java/io/vertx/core/internal/http/HttpConnectionInternal.java diff --git a/vertx-core/src/main/java/io/vertx/core/http/impl/http1/Http1Connection.java b/vertx-core/src/main/java/io/vertx/core/http/impl/http1/Http1Connection.java index c06b2394fce..99c17eebf5b 100644 --- a/vertx-core/src/main/java/io/vertx/core/http/impl/http1/Http1Connection.java +++ b/vertx-core/src/main/java/io/vertx/core/http/impl/http1/Http1Connection.java @@ -26,6 +26,7 @@ import io.vertx.core.buffer.Buffer; import io.vertx.core.http.*; import io.vertx.core.internal.ContextInternal; +import io.vertx.core.internal.http.HttpConnectionInternal; import io.vertx.core.net.impl.VertxConnection; import java.time.Duration; @@ -33,7 +34,7 @@ /** * @author Julien Viet */ -abstract class Http1Connection extends VertxConnection implements io.vertx.core.http.HttpConnection { +abstract class Http1Connection extends VertxConnection implements HttpConnectionInternal { protected boolean closeInitiated; protected Duration shutdownInitiated; diff --git a/vertx-core/src/main/java/io/vertx/core/http/impl/http2/codec/Http2ConnectionImpl.java b/vertx-core/src/main/java/io/vertx/core/http/impl/http2/codec/Http2ConnectionImpl.java index 796f51ff5f5..b01e0716d48 100644 --- a/vertx-core/src/main/java/io/vertx/core/http/impl/http2/codec/Http2ConnectionImpl.java +++ b/vertx-core/src/main/java/io/vertx/core/http/impl/http2/codec/Http2ConnectionImpl.java @@ -36,6 +36,7 @@ import io.vertx.core.impl.buffer.VertxByteBufAllocator; import io.vertx.core.internal.ContextInternal; import io.vertx.core.internal.PromiseInternal; +import io.vertx.core.internal.http.HttpConnectionInternal; import io.vertx.core.internal.logging.Logger; import io.vertx.core.internal.logging.LoggerFactory; import io.vertx.core.net.impl.ConnectionBase; @@ -49,7 +50,7 @@ /** * @author Julien Viet */ -abstract class Http2ConnectionImpl extends ConnectionBase implements Http2FrameListener, HttpConnection, Http2Connection { +abstract class Http2ConnectionImpl extends ConnectionBase implements Http2FrameListener, HttpConnectionInternal, Http2Connection { private static final Logger log = LoggerFactory.getLogger(Http2ConnectionImpl.class); diff --git a/vertx-core/src/main/java/io/vertx/core/http/impl/http2/multiplex/Http2MultiplexConnection.java b/vertx-core/src/main/java/io/vertx/core/http/impl/http2/multiplex/Http2MultiplexConnection.java index c51f3c090d5..959a4ce0d79 100644 --- a/vertx-core/src/main/java/io/vertx/core/http/impl/http2/multiplex/Http2MultiplexConnection.java +++ b/vertx-core/src/main/java/io/vertx/core/http/impl/http2/multiplex/Http2MultiplexConnection.java @@ -47,6 +47,7 @@ import io.vertx.core.internal.ContextInternal; import io.vertx.core.internal.PromiseInternal; import io.vertx.core.internal.buffer.BufferInternal; +import io.vertx.core.internal.http.HttpConnectionInternal; import io.vertx.core.net.HostAndPort; import io.vertx.core.net.impl.ConnectionBase; import io.vertx.core.spi.metrics.NetworkMetrics; @@ -57,7 +58,7 @@ import java.util.Deque; import java.util.Objects; -public abstract class Http2MultiplexConnection extends ConnectionBase implements HttpConnection { +public abstract class Http2MultiplexConnection extends ConnectionBase implements HttpConnectionInternal { protected final Http2MultiplexHandler handler; protected final TransportMetrics transportMetrics; diff --git a/vertx-core/src/main/java/io/vertx/core/http/impl/http3/Http3Connection.java b/vertx-core/src/main/java/io/vertx/core/http/impl/http3/Http3Connection.java index 49d9b13321b..f4191bc7764 100644 --- a/vertx-core/src/main/java/io/vertx/core/http/impl/http3/Http3Connection.java +++ b/vertx-core/src/main/java/io/vertx/core/http/impl/http3/Http3Connection.java @@ -26,6 +26,7 @@ import io.vertx.core.http.Http3Settings; import io.vertx.core.internal.ContextInternal; import io.vertx.core.internal.PromiseInternal; +import io.vertx.core.internal.http.HttpConnectionInternal; import io.vertx.core.internal.quic.QuicConnectionInternal; import io.vertx.core.internal.quic.QuicStreamInternal; import io.vertx.core.net.SocketAddress; @@ -33,6 +34,7 @@ import javax.net.ssl.SSLSession; import java.time.Duration; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -40,7 +42,7 @@ /** * @author Julien Viet */ -public abstract class Http3Connection implements HttpConnection { +public abstract class Http3Connection implements HttpConnectionInternal { private final LongObjectMap> streams; private final Http3FrameLogger frameLogger; @@ -55,6 +57,7 @@ public abstract class Http3Connection implements HttpConnection { private Http3Settings localSettings; private Http3Settings remoteSettings; private Handler remoteSettingsHandler; + private Map attachments; public Http3Connection(QuicConnectionInternal connection, Http3Settings localSettings, Http3FrameLogger frameLogger) { this.streams = new LongObjectHashMap<>(); @@ -291,12 +294,27 @@ private void handleGrace(QuicStreamChannel localControlStream) { } protected void handleClosed() { + attachments = null; Handler handler = closeHandler; if (handler != null) { context.emit(null, handler); } } + @SuppressWarnings("unchecked") + @Override + public T get(Object key) { + return attachments != null ? (T) attachments.get(key) : null; + } + + @Override + public void set(Object key, Object value) { + if (attachments == null) { + attachments = new HashMap<>(); + } + attachments.put(key, value); + } + private void sendGoAway(QuicStreamChannel controlStream, long streamId, PromiseInternal promise) { Iterator>> iterator = streams.entries().iterator(); List> toCancel = new ArrayList<>(); diff --git a/vertx-core/src/main/java/io/vertx/core/internal/http/HttpConnectionInternal.java b/vertx-core/src/main/java/io/vertx/core/internal/http/HttpConnectionInternal.java new file mode 100644 index 00000000000..5e15654cea8 --- /dev/null +++ b/vertx-core/src/main/java/io/vertx/core/internal/http/HttpConnectionInternal.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2011-2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.internal.http; + +import io.vertx.core.http.HttpConnection; + +/** + * Extends to expose internal methods that are necessary for integration. + */ +public interface HttpConnectionInternal extends HttpConnection { + + /** + * Get an attachment previously stored with {@link #set(Object, Object)}. + *

+ * Attachments are scoped to the lifecycle of the connection: they are discarded when the + * connection closes and do not need to be cleaned up manually. + * + * @param key the attachment key + * @return the attachment value, or {@code null} if absent + */ + T get(Object key); + + /** + * Store an attachment on this connection, associated with {@code key}. + * + * @param key the attachment key + * @param value the attachment value + */ + void set(Object key, Object value); + +} diff --git a/vertx-core/src/main/java/io/vertx/core/net/impl/ConnectionBase.java b/vertx-core/src/main/java/io/vertx/core/net/impl/ConnectionBase.java index 2a294adf121..576389c7ec0 100644 --- a/vertx-core/src/main/java/io/vertx/core/net/impl/ConnectionBase.java +++ b/vertx-core/src/main/java/io/vertx/core/net/impl/ConnectionBase.java @@ -33,6 +33,7 @@ import javax.net.ssl.SSLSession; import java.net.InetSocketAddress; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -74,6 +75,7 @@ public abstract class ConnectionBase { private Future closeFuture; private long remainingBytesRead; private long remainingBytesWritten; + private Map attachments; protected ConnectionBase(ContextInternal context, ChannelHandlerContext chctx) { @@ -156,6 +158,24 @@ public final synchronized Object metric() { return metric; } + /** + * Get an attachment previously stored with {@link #set(Object, Object)}. + */ + @SuppressWarnings("unchecked") + public final synchronized T get(Object key) { + return attachments != null ? (T) attachments.get(key) : null; + } + + /** + * Store an attachment on this connection, associated with {@code key}. + */ + public final synchronized void set(Object key, Object value) { + if (attachments == null) { + attachments = new HashMap<>(); + } + attachments.put(key, value); + } + public NetworkMetrics metrics() { return null; } @@ -184,6 +204,9 @@ protected boolean handleException(Throwable t) { } protected void handleClosed() { + synchronized (this) { + attachments = null; + } NetworkMetrics metrics = metrics(); if (metrics != null) { flushBytesRead(); From b8cce3d7a797d3ca255ce2fcb95cd1d13b95ddb5 Mon Sep 17 00:00:00 2001 From: Harshal Parekh Date: Fri, 10 Jul 2026 19:44:52 -0500 Subject: [PATCH 2/4] renaming --- .../java/io/vertx/core/http/impl/http3/Http3Connection.java | 4 ++-- .../io/vertx/core/internal/http/HttpConnectionInternal.java | 6 +++--- .../main/java/io/vertx/core/net/impl/ConnectionBase.java | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/vertx-core/src/main/java/io/vertx/core/http/impl/http3/Http3Connection.java b/vertx-core/src/main/java/io/vertx/core/http/impl/http3/Http3Connection.java index f4191bc7764..71a30a1f491 100644 --- a/vertx-core/src/main/java/io/vertx/core/http/impl/http3/Http3Connection.java +++ b/vertx-core/src/main/java/io/vertx/core/http/impl/http3/Http3Connection.java @@ -303,12 +303,12 @@ protected void handleClosed() { @SuppressWarnings("unchecked") @Override - public T get(Object key) { + public T attachment(Object key) { return attachments != null ? (T) attachments.get(key) : null; } @Override - public void set(Object key, Object value) { + public void attach(Object key, Object value) { if (attachments == null) { attachments = new HashMap<>(); } diff --git a/vertx-core/src/main/java/io/vertx/core/internal/http/HttpConnectionInternal.java b/vertx-core/src/main/java/io/vertx/core/internal/http/HttpConnectionInternal.java index 5e15654cea8..bb72b29c79f 100644 --- a/vertx-core/src/main/java/io/vertx/core/internal/http/HttpConnectionInternal.java +++ b/vertx-core/src/main/java/io/vertx/core/internal/http/HttpConnectionInternal.java @@ -18,7 +18,7 @@ public interface HttpConnectionInternal extends HttpConnection { /** - * Get an attachment previously stored with {@link #set(Object, Object)}. + * Get an attachment previously stored with {@link #attach(Object, Object)}. *

* Attachments are scoped to the lifecycle of the connection: they are discarded when the * connection closes and do not need to be cleaned up manually. @@ -26,7 +26,7 @@ public interface HttpConnectionInternal extends HttpConnection { * @param key the attachment key * @return the attachment value, or {@code null} if absent */ - T get(Object key); + T attachment(Object key); /** * Store an attachment on this connection, associated with {@code key}. @@ -34,6 +34,6 @@ public interface HttpConnectionInternal extends HttpConnection { * @param key the attachment key * @param value the attachment value */ - void set(Object key, Object value); + void attach(Object key, Object value); } diff --git a/vertx-core/src/main/java/io/vertx/core/net/impl/ConnectionBase.java b/vertx-core/src/main/java/io/vertx/core/net/impl/ConnectionBase.java index 576389c7ec0..62dd4b8439a 100644 --- a/vertx-core/src/main/java/io/vertx/core/net/impl/ConnectionBase.java +++ b/vertx-core/src/main/java/io/vertx/core/net/impl/ConnectionBase.java @@ -159,17 +159,17 @@ public final synchronized Object metric() { } /** - * Get an attachment previously stored with {@link #set(Object, Object)}. + * Get an attachment previously stored with {@link #attach(Object, Object)}. */ @SuppressWarnings("unchecked") - public final synchronized T get(Object key) { + public final synchronized T attachment(Object key) { return attachments != null ? (T) attachments.get(key) : null; } /** * Store an attachment on this connection, associated with {@code key}. */ - public final synchronized void set(Object key, Object value) { + public final synchronized void attach(Object key, Object value) { if (attachments == null) { attachments = new HashMap<>(); } From 7dbf9517a2bba1581e766855ff7da7a34e0bda04 Mon Sep 17 00:00:00 2001 From: Harshal Parekh Date: Mon, 20 Jul 2026 10:20:41 -0500 Subject: [PATCH 3/4] address comments --- .../core/http/impl/HttpClientConnection.java | 4 +-- .../core/http/impl/HttpServerConnection.java | 4 +-- .../core/http/impl/http1/Http1Connection.java | 26 ++++++++++++-- .../impl/http2/codec/Http2ConnectionImpl.java | 20 +++++++++-- .../multiplex/Http2MultiplexConnection.java | 26 ++++++++++++-- .../core/http/impl/http3/Http3Connection.java | 13 ++++--- .../tcp/Http2UpgradeClientConnection.java | 36 ++++++++++++++++--- .../vertx/core/net/impl/ConnectionBase.java | 23 ------------ .../io/vertx/tests/http/Http2ClientTest.java | 36 +++++++++++++++++++ 9 files changed, 143 insertions(+), 45 deletions(-) diff --git a/vertx-core/src/main/java/io/vertx/core/http/impl/HttpClientConnection.java b/vertx-core/src/main/java/io/vertx/core/http/impl/HttpClientConnection.java index ffc7e8f99f5..d7e118f28ce 100644 --- a/vertx-core/src/main/java/io/vertx/core/http/impl/HttpClientConnection.java +++ b/vertx-core/src/main/java/io/vertx/core/http/impl/HttpClientConnection.java @@ -15,8 +15,8 @@ import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.MultiMap; -import io.vertx.core.http.HttpConnection; import io.vertx.core.internal.ContextInternal; +import io.vertx.core.internal.http.HttpConnectionInternal; import io.vertx.core.internal.logging.Logger; import io.vertx.core.internal.logging.LoggerFactory; import io.vertx.core.net.HostAndPort; @@ -24,7 +24,7 @@ /** * @author Julien Viet */ -public interface HttpClientConnection extends HttpConnection { +public interface HttpClientConnection extends HttpConnectionInternal { Logger log = LoggerFactory.getLogger(HttpClientConnection.class); diff --git a/vertx-core/src/main/java/io/vertx/core/http/impl/HttpServerConnection.java b/vertx-core/src/main/java/io/vertx/core/http/impl/HttpServerConnection.java index 960ceb7f322..cb9774622c4 100644 --- a/vertx-core/src/main/java/io/vertx/core/http/impl/HttpServerConnection.java +++ b/vertx-core/src/main/java/io/vertx/core/http/impl/HttpServerConnection.java @@ -13,13 +13,13 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.Headers; import io.vertx.core.Handler; -import io.vertx.core.http.HttpConnection; import io.vertx.core.internal.ContextInternal; +import io.vertx.core.internal.http.HttpConnectionInternal; /** * @author Julien Viet */ -public interface HttpServerConnection extends HttpConnection { +public interface HttpServerConnection extends HttpConnectionInternal { HttpServerConnection streamHandler(Handler handler); diff --git a/vertx-core/src/main/java/io/vertx/core/http/impl/http1/Http1Connection.java b/vertx-core/src/main/java/io/vertx/core/http/impl/http1/Http1Connection.java index 99c17eebf5b..f35485f8f38 100644 --- a/vertx-core/src/main/java/io/vertx/core/http/impl/http1/Http1Connection.java +++ b/vertx-core/src/main/java/io/vertx/core/http/impl/http1/Http1Connection.java @@ -26,21 +26,23 @@ import io.vertx.core.buffer.Buffer; import io.vertx.core.http.*; import io.vertx.core.internal.ContextInternal; -import io.vertx.core.internal.http.HttpConnectionInternal; import io.vertx.core.net.impl.VertxConnection; import java.time.Duration; +import java.util.HashMap; +import java.util.Map; /** * @author Julien Viet */ -abstract class Http1Connection extends VertxConnection implements HttpConnectionInternal { +abstract class Http1Connection extends VertxConnection implements HttpConnection { protected boolean closeInitiated; protected Duration shutdownInitiated; protected ChannelPromise closePromise; private Handler shutdownHandler; + private Map attachments; Http1Connection(ContextInternal context, ChannelHandlerContext chctx) { this(context, chctx, false); @@ -143,6 +145,26 @@ public Future ping(Buffer data) { throw new UnsupportedOperationException("HTTP/1.x connections don't support PING"); } + @Override + protected void handleClosed() { + synchronized (this) { + attachments = null; + } + super.handleClosed(); + } + + @SuppressWarnings("unchecked") + public synchronized T attachment(Object key) { + return attachments != null ? (T) attachments.get(key) : null; + } + + public synchronized void attach(Object key, Object value) { + if (attachments == null) { + attachments = new HashMap<>(); + } + attachments.put(key, value); + } + @Override protected long sizeof(Object obj) { // https://github.com/netty/netty/issues/12708 diff --git a/vertx-core/src/main/java/io/vertx/core/http/impl/http2/codec/Http2ConnectionImpl.java b/vertx-core/src/main/java/io/vertx/core/http/impl/http2/codec/Http2ConnectionImpl.java index b01e0716d48..8983e67852b 100644 --- a/vertx-core/src/main/java/io/vertx/core/http/impl/http2/codec/Http2ConnectionImpl.java +++ b/vertx-core/src/main/java/io/vertx/core/http/impl/http2/codec/Http2ConnectionImpl.java @@ -36,7 +36,6 @@ import io.vertx.core.impl.buffer.VertxByteBufAllocator; import io.vertx.core.internal.ContextInternal; import io.vertx.core.internal.PromiseInternal; -import io.vertx.core.internal.http.HttpConnectionInternal; import io.vertx.core.internal.logging.Logger; import io.vertx.core.internal.logging.LoggerFactory; import io.vertx.core.net.impl.ConnectionBase; @@ -44,13 +43,14 @@ import java.time.Duration; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.HashMap; import java.util.Map; import java.util.Objects; /** * @author Julien Viet */ -abstract class Http2ConnectionImpl extends ConnectionBase implements Http2FrameListener, HttpConnectionInternal, Http2Connection { +abstract class Http2ConnectionImpl extends ConnectionBase implements Http2FrameListener, HttpConnection, Http2Connection { private static final Logger log = LoggerFactory.getLogger(Http2ConnectionImpl.class); @@ -75,6 +75,7 @@ private static ByteBuf safeBuffer(ByteBuf buf) { private GoAway goAwayStatus; private int windowSize; private long maxConcurrentStreams; + private Map attachments; public Http2ConnectionImpl(ContextInternal context, VertxHttp2ConnectionHandler handler) { super(context, handler.context()); @@ -88,9 +89,24 @@ public Http2ConnectionImpl(ContextInternal context, VertxHttp2ConnectionHandler @Override public void handleClosed() { + synchronized (this) { + attachments = null; + } super.handleClosed(); } + @SuppressWarnings("unchecked") + public synchronized T attachment(Object key) { + return attachments != null ? (T) attachments.get(key) : null; + } + + public synchronized void attach(Object key, Object value) { + if (attachments == null) { + attachments = new HashMap<>(); + } + attachments.put(key, value); + } + protected void handleIdle(IdleStateEvent event) { log.debug("The connection will be closed due to timeout"); chctx.close(); diff --git a/vertx-core/src/main/java/io/vertx/core/http/impl/http2/multiplex/Http2MultiplexConnection.java b/vertx-core/src/main/java/io/vertx/core/http/impl/http2/multiplex/Http2MultiplexConnection.java index 959a4ce0d79..a6131332e64 100644 --- a/vertx-core/src/main/java/io/vertx/core/http/impl/http2/multiplex/Http2MultiplexConnection.java +++ b/vertx-core/src/main/java/io/vertx/core/http/impl/http2/multiplex/Http2MultiplexConnection.java @@ -47,7 +47,6 @@ import io.vertx.core.internal.ContextInternal; import io.vertx.core.internal.PromiseInternal; import io.vertx.core.internal.buffer.BufferInternal; -import io.vertx.core.internal.http.HttpConnectionInternal; import io.vertx.core.net.HostAndPort; import io.vertx.core.net.impl.ConnectionBase; import io.vertx.core.spi.metrics.NetworkMetrics; @@ -56,9 +55,11 @@ import java.time.Duration; import java.util.ArrayDeque; import java.util.Deque; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; -public abstract class Http2MultiplexConnection extends ConnectionBase implements HttpConnectionInternal { +public abstract class Http2MultiplexConnection extends ConnectionBase implements HttpConnection { protected final Http2MultiplexHandler handler; protected final TransportMetrics transportMetrics; @@ -70,6 +71,7 @@ public abstract class Http2MultiplexConnection extends Co private Handler shutdownHandler; private Handler goAwayHandler; private Handler pingHandler; + private Map attachments; public Http2MultiplexConnection(Http2MultiplexHandler handler, TransportMetrics transportMetrics, ChannelHandlerContext chctx, ContextInternal context) { super(context, chctx); @@ -419,6 +421,26 @@ void onClose() { handleClosed(); } + @Override + protected void handleClosed() { + synchronized (this) { + attachments = null; + } + super.handleClosed(); + } + + @SuppressWarnings("unchecked") + public synchronized T attachment(Object key) { + return attachments != null ? (T) attachments.get(key) : null; + } + + public synchronized void attach(Object key, Object value) { + if (attachments == null) { + attachments = new HashMap<>(); + } + attachments.put(key, value); + } + void onException(Throwable err) { handleException(err); if (err instanceof Http2Exception) { diff --git a/vertx-core/src/main/java/io/vertx/core/http/impl/http3/Http3Connection.java b/vertx-core/src/main/java/io/vertx/core/http/impl/http3/Http3Connection.java index 71a30a1f491..421595640f2 100644 --- a/vertx-core/src/main/java/io/vertx/core/http/impl/http3/Http3Connection.java +++ b/vertx-core/src/main/java/io/vertx/core/http/impl/http3/Http3Connection.java @@ -26,7 +26,6 @@ import io.vertx.core.http.Http3Settings; import io.vertx.core.internal.ContextInternal; import io.vertx.core.internal.PromiseInternal; -import io.vertx.core.internal.http.HttpConnectionInternal; import io.vertx.core.internal.quic.QuicConnectionInternal; import io.vertx.core.internal.quic.QuicStreamInternal; import io.vertx.core.net.SocketAddress; @@ -42,7 +41,7 @@ /** * @author Julien Viet */ -public abstract class Http3Connection implements HttpConnectionInternal { +public abstract class Http3Connection implements HttpConnection { private final LongObjectMap> streams; private final Http3FrameLogger frameLogger; @@ -294,7 +293,9 @@ private void handleGrace(QuicStreamChannel localControlStream) { } protected void handleClosed() { - attachments = null; + synchronized (this) { + attachments = null; + } Handler handler = closeHandler; if (handler != null) { context.emit(null, handler); @@ -302,13 +303,11 @@ protected void handleClosed() { } @SuppressWarnings("unchecked") - @Override - public T attachment(Object key) { + public synchronized T attachment(Object key) { return attachments != null ? (T) attachments.get(key) : null; } - @Override - public void attach(Object key, Object value) { + public synchronized void attach(Object key, Object value) { if (attachments == null) { attachments = new HashMap<>(); } diff --git a/vertx-core/src/main/java/io/vertx/core/http/impl/tcp/Http2UpgradeClientConnection.java b/vertx-core/src/main/java/io/vertx/core/http/impl/tcp/Http2UpgradeClientConnection.java index 7eeed6ccabe..e43afe85fb9 100644 --- a/vertx-core/src/main/java/io/vertx/core/http/impl/tcp/Http2UpgradeClientConnection.java +++ b/vertx-core/src/main/java/io/vertx/core/http/impl/tcp/Http2UpgradeClientConnection.java @@ -38,6 +38,7 @@ import javax.net.ssl.SSLSession; import java.time.Duration; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -56,6 +57,7 @@ public class Http2UpgradeClientConnection implements io.vertx.core.http.impl.Htt private final ClientMetrics clientMetrics; private io.vertx.core.http.impl.HttpClientConnection current; private boolean upgradeProcessed; + private Map attachments; private Handler closeHandler; private Handler shutdownHandler; @@ -73,6 +75,7 @@ public Http2UpgradeClientConnection(Http1ClientConnection connection, ClientMetr this.current = connection; this.upgrade = upgrade; this.clientMetrics = clientMetrics; + connection.closeHandler(this::handleClosed); } public io.vertx.core.http.impl.HttpClientConnection unwrap() { @@ -334,7 +337,7 @@ void handleUpgrade(io.vertx.core.http.impl.HttpClientConnection conn, HttpClient pushHandler = null; closeHandler = null; upgradedConnection.current = conn; - conn.closeHandler(upgradedConnection.closeHandler); + conn.closeHandler(upgradedConnection::handleClosed); conn.exceptionHandler(upgradedConnection.exceptionHandler); conn.pingHandler(upgradedConnection.pingHandler); conn.goAwayHandler(upgradedConnection.goAwayHandler); @@ -776,13 +779,22 @@ public HttpConnection shutdownHandler(@Nullable Handler handler) { @Override public HttpConnection closeHandler(Handler handler) { - if (current instanceof Http1ClientConnection) { - closeHandler = handler; - } - current.closeHandler(handler); + closeHandler = handler; + current.closeHandler(this::handleClosed); return this; } + private void handleClosed(Void event) { + Handler handler; + synchronized (this) { + attachments = null; + handler = closeHandler; + } + if (handler != null) { + handler.handle(event); + } + } + @Override public HttpConnection exceptionHandler(Handler handler) { if (current instanceof Http1ClientConnection) { @@ -913,6 +925,20 @@ public String toString() { return getClass().getSimpleName() + "[current=" + current.getClass().getSimpleName() + "]"; } + @Override + @SuppressWarnings("unchecked") + public synchronized T attachment(Object key) { + return attachments != null ? (T) attachments.get(key) : null; + } + + @Override + public synchronized void attach(Object key, Object value) { + if (attachments == null) { + attachments = new HashMap<>(); + } + attachments.put(key, value); + } + /** * The outcome of the upgrade signalled by the upgrade. */ diff --git a/vertx-core/src/main/java/io/vertx/core/net/impl/ConnectionBase.java b/vertx-core/src/main/java/io/vertx/core/net/impl/ConnectionBase.java index 62dd4b8439a..2a294adf121 100644 --- a/vertx-core/src/main/java/io/vertx/core/net/impl/ConnectionBase.java +++ b/vertx-core/src/main/java/io/vertx/core/net/impl/ConnectionBase.java @@ -33,7 +33,6 @@ import javax.net.ssl.SSLSession; import java.net.InetSocketAddress; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -75,7 +74,6 @@ public abstract class ConnectionBase { private Future closeFuture; private long remainingBytesRead; private long remainingBytesWritten; - private Map attachments; protected ConnectionBase(ContextInternal context, ChannelHandlerContext chctx) { @@ -158,24 +156,6 @@ public final synchronized Object metric() { return metric; } - /** - * Get an attachment previously stored with {@link #attach(Object, Object)}. - */ - @SuppressWarnings("unchecked") - public final synchronized T attachment(Object key) { - return attachments != null ? (T) attachments.get(key) : null; - } - - /** - * Store an attachment on this connection, associated with {@code key}. - */ - public final synchronized void attach(Object key, Object value) { - if (attachments == null) { - attachments = new HashMap<>(); - } - attachments.put(key, value); - } - public NetworkMetrics metrics() { return null; } @@ -204,9 +184,6 @@ protected boolean handleException(Throwable t) { } protected void handleClosed() { - synchronized (this) { - attachments = null; - } NetworkMetrics metrics = metrics(); if (metrics != null) { flushBytesRead(); diff --git a/vertx-core/src/test/java/io/vertx/tests/http/Http2ClientTest.java b/vertx-core/src/test/java/io/vertx/tests/http/Http2ClientTest.java index bb7a54f0c85..3500c83e365 100644 --- a/vertx-core/src/test/java/io/vertx/tests/http/Http2ClientTest.java +++ b/vertx-core/src/test/java/io/vertx/tests/http/Http2ClientTest.java @@ -30,6 +30,7 @@ import io.vertx.core.http.Http2Settings; import io.vertx.core.internal.ContextInternal; import io.vertx.core.internal.buffer.BufferInternal; +import io.vertx.core.internal.http.HttpConnectionInternal; import io.vertx.core.http.impl.tcp.Http2UpgradeClientConnection; import io.vertx.core.http.impl.HttpClientConnection; import io.vertx.core.net.NetServer; @@ -1729,6 +1730,41 @@ public void testClearTextUpgrade() throws Exception { Assert.assertEquals(Arrays.asList("GET", "GET"), requests); } + @Test + public void testClearTextUpgradePreservesConnectionAttachment() throws Exception { + Assume.assumeTrue(testAddress.isInetSocket()); + Object key = new Object(); + Object value = new Object(); + ServerBootstrap bootstrap = createH2CServer((dec, enc) -> new Http2EventAdapter() { + @Override + public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, short weight, boolean exclusive, int padding, boolean endStream) throws Http2Exception { + enc.writeHeaders(ctx, streamId, new DefaultHttp2Headers().status("200"), 0, true, ctx.newPromise()); + ctx.flush(); + } + }, upgrade -> { + }, true); + ChannelFuture channel = bootstrap.bind(DEFAULT_HTTPS_HOST, DEFAULT_HTTPS_PORT).sync(); + try { + client.close(); + client = vertx.createHttpClient(clientOptions + .setUseAlpn(false) + .setSsl(false) + .setHttp2ClearTextUpgrade(true)); + client.request(requestOptions).onComplete(TestUtils.onSuccess(request -> { + HttpConnectionInternal connection = (HttpConnectionInternal) request.connection(); + connection.attach(key, value); + request.send().onComplete(TestUtils.onSuccess(response -> { + Assert.assertSame(connection, response.request().connection()); + Assert.assertSame(value, connection.attachment(key)); + testComplete(); + })); + })); + await(); + } finally { + channel.channel().close().sync(); + } + } + @Test public void testClearTextUpgradeWithPreflightRequest() throws Exception { List requests = testClearText(true, true); From 73c8713a79e8e573e34916dd22b3f316b642eedb Mon Sep 17 00:00:00 2001 From: Harshal Parekh Date: Mon, 27 Jul 2026 12:19:56 -0500 Subject: [PATCH 4/4] use concurrent hasmap --- .../impl/http2/codec/Http2ConnectionImpl.java | 17 ++++++----------- .../multiplex/Http2MultiplexConnection.java | 17 ++++++----------- .../core/http/impl/http3/Http3Connection.java | 17 ++++++----------- .../impl/tcp/Http2UpgradeClientConnection.java | 15 ++++++--------- 4 files changed, 24 insertions(+), 42 deletions(-) diff --git a/vertx-core/src/main/java/io/vertx/core/http/impl/http2/codec/Http2ConnectionImpl.java b/vertx-core/src/main/java/io/vertx/core/http/impl/http2/codec/Http2ConnectionImpl.java index 8983e67852b..8fd4d5e743a 100644 --- a/vertx-core/src/main/java/io/vertx/core/http/impl/http2/codec/Http2ConnectionImpl.java +++ b/vertx-core/src/main/java/io/vertx/core/http/impl/http2/codec/Http2ConnectionImpl.java @@ -43,7 +43,7 @@ import java.time.Duration; import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; import java.util.Map; import java.util.Objects; @@ -75,7 +75,7 @@ private static ByteBuf safeBuffer(ByteBuf buf) { private GoAway goAwayStatus; private int windowSize; private long maxConcurrentStreams; - private Map attachments; + private final Map attachments = new ConcurrentHashMap<>(); public Http2ConnectionImpl(ContextInternal context, VertxHttp2ConnectionHandler handler) { super(context, handler.context()); @@ -89,21 +89,16 @@ public Http2ConnectionImpl(ContextInternal context, VertxHttp2ConnectionHandler @Override public void handleClosed() { - synchronized (this) { - attachments = null; - } + attachments.clear(); super.handleClosed(); } @SuppressWarnings("unchecked") - public synchronized T attachment(Object key) { - return attachments != null ? (T) attachments.get(key) : null; + public T attachment(Object key) { + return (T) attachments.get(key); } - public synchronized void attach(Object key, Object value) { - if (attachments == null) { - attachments = new HashMap<>(); - } + public void attach(Object key, Object value) { attachments.put(key, value); } diff --git a/vertx-core/src/main/java/io/vertx/core/http/impl/http2/multiplex/Http2MultiplexConnection.java b/vertx-core/src/main/java/io/vertx/core/http/impl/http2/multiplex/Http2MultiplexConnection.java index a6131332e64..efa2444a69d 100644 --- a/vertx-core/src/main/java/io/vertx/core/http/impl/http2/multiplex/Http2MultiplexConnection.java +++ b/vertx-core/src/main/java/io/vertx/core/http/impl/http2/multiplex/Http2MultiplexConnection.java @@ -55,7 +55,7 @@ import java.time.Duration; import java.util.ArrayDeque; import java.util.Deque; -import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; import java.util.Map; import java.util.Objects; @@ -71,7 +71,7 @@ public abstract class Http2MultiplexConnection extends Co private Handler shutdownHandler; private Handler goAwayHandler; private Handler pingHandler; - private Map attachments; + private final Map attachments = new ConcurrentHashMap<>(); public Http2MultiplexConnection(Http2MultiplexHandler handler, TransportMetrics transportMetrics, ChannelHandlerContext chctx, ContextInternal context) { super(context, chctx); @@ -423,21 +423,16 @@ void onClose() { @Override protected void handleClosed() { - synchronized (this) { - attachments = null; - } + attachments.clear(); super.handleClosed(); } @SuppressWarnings("unchecked") - public synchronized T attachment(Object key) { - return attachments != null ? (T) attachments.get(key) : null; + public T attachment(Object key) { + return (T) attachments.get(key); } - public synchronized void attach(Object key, Object value) { - if (attachments == null) { - attachments = new HashMap<>(); - } + public void attach(Object key, Object value) { attachments.put(key, value); } diff --git a/vertx-core/src/main/java/io/vertx/core/http/impl/http3/Http3Connection.java b/vertx-core/src/main/java/io/vertx/core/http/impl/http3/Http3Connection.java index 421595640f2..7ec198fd7fc 100644 --- a/vertx-core/src/main/java/io/vertx/core/http/impl/http3/Http3Connection.java +++ b/vertx-core/src/main/java/io/vertx/core/http/impl/http3/Http3Connection.java @@ -33,7 +33,7 @@ import javax.net.ssl.SSLSession; import java.time.Duration; import java.util.ArrayList; -import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -56,7 +56,7 @@ public abstract class Http3Connection implements HttpConnection { private Http3Settings localSettings; private Http3Settings remoteSettings; private Handler remoteSettingsHandler; - private Map attachments; + private final Map attachments = new ConcurrentHashMap<>(); public Http3Connection(QuicConnectionInternal connection, Http3Settings localSettings, Http3FrameLogger frameLogger) { this.streams = new LongObjectHashMap<>(); @@ -293,9 +293,7 @@ private void handleGrace(QuicStreamChannel localControlStream) { } protected void handleClosed() { - synchronized (this) { - attachments = null; - } + attachments.clear(); Handler handler = closeHandler; if (handler != null) { context.emit(null, handler); @@ -303,14 +301,11 @@ protected void handleClosed() { } @SuppressWarnings("unchecked") - public synchronized T attachment(Object key) { - return attachments != null ? (T) attachments.get(key) : null; + public T attachment(Object key) { + return (T) attachments.get(key); } - public synchronized void attach(Object key, Object value) { - if (attachments == null) { - attachments = new HashMap<>(); - } + public void attach(Object key, Object value) { attachments.put(key, value); } diff --git a/vertx-core/src/main/java/io/vertx/core/http/impl/tcp/Http2UpgradeClientConnection.java b/vertx-core/src/main/java/io/vertx/core/http/impl/tcp/Http2UpgradeClientConnection.java index e43afe85fb9..0211fc8420e 100644 --- a/vertx-core/src/main/java/io/vertx/core/http/impl/tcp/Http2UpgradeClientConnection.java +++ b/vertx-core/src/main/java/io/vertx/core/http/impl/tcp/Http2UpgradeClientConnection.java @@ -38,7 +38,7 @@ import javax.net.ssl.SSLSession; import java.time.Duration; -import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; import java.util.List; import java.util.Map; @@ -57,7 +57,7 @@ public class Http2UpgradeClientConnection implements io.vertx.core.http.impl.Htt private final ClientMetrics clientMetrics; private io.vertx.core.http.impl.HttpClientConnection current; private boolean upgradeProcessed; - private Map attachments; + private final Map attachments = new ConcurrentHashMap<>(); private Handler closeHandler; private Handler shutdownHandler; @@ -785,9 +785,9 @@ public HttpConnection closeHandler(Handler handler) { } private void handleClosed(Void event) { + attachments.clear(); Handler handler; synchronized (this) { - attachments = null; handler = closeHandler; } if (handler != null) { @@ -927,15 +927,12 @@ public String toString() { @Override @SuppressWarnings("unchecked") - public synchronized T attachment(Object key) { - return attachments != null ? (T) attachments.get(key) : null; + public T attachment(Object key) { + return (T) attachments.get(key); } @Override - public synchronized void attach(Object key, Object value) { - if (attachments == null) { - attachments = new HashMap<>(); - } + public void attach(Object key, Object value) { attachments.put(key, value); }