From 3ff33786dbb1eee2edbd31a66739e47a96999819 Mon Sep 17 00:00:00 2001 From: Aleksa Radovanovic Date: Fri, 22 Mar 2024 15:44:55 +0100 Subject: [PATCH 1/4] Cherry-pick from 4.x: Supports new NetServer options: tcpKeepAliveCount, tcpKeepAliveIdleSeconds, tcpKeepAliveIntervalSeconds --- .../core/net/NetServerOptionsConverter.java | 18 ++++ .../io/vertx/core/net/NetServerOptions.java | 96 +++++++++++++++++-- .../vertx/core/spi/transport/Transport.java | 21 ++-- .../test/java/io/vertx/tests/net/NetTest.java | 38 +++++++- 4 files changed, 159 insertions(+), 14 deletions(-) diff --git a/vertx-core/src/main/generated/io/vertx/core/net/NetServerOptionsConverter.java b/vertx-core/src/main/generated/io/vertx/core/net/NetServerOptionsConverter.java index 7394e8a7f5f..cf593842d65 100644 --- a/vertx-core/src/main/generated/io/vertx/core/net/NetServerOptionsConverter.java +++ b/vertx-core/src/main/generated/io/vertx/core/net/NetServerOptionsConverter.java @@ -59,6 +59,21 @@ static void fromJson(Iterable> json, NetServ obj.setTrafficShapingOptions(new io.vertx.core.net.TrafficShapingOptions((io.vertx.core.json.JsonObject)member.getValue())); } break; + case "tcpKeepAliveIdleSeconds": + if (member.getValue() instanceof Number) { + obj.setTcpKeepAliveIdleSeconds(((Number)member.getValue()).intValue()); + } + break; + case "tcpKeepAliveCount": + if (member.getValue() instanceof Number) { + obj.setTcpKeepAliveCount(((Number)member.getValue()).intValue()); + } + break; + case "tcpKeepAliveIntervalSeconds": + if (member.getValue() instanceof Number) { + obj.setTcpKeepAliveIntervalSeconds(((Number)member.getValue()).intValue()); + } + break; case "registerWriteHandler": if (member.getValue() instanceof Boolean) { obj.setRegisterWriteHandler((Boolean)member.getValue()); @@ -90,6 +105,9 @@ static void toJson(NetServerOptions obj, java.util.Map json) { if (obj.getTrafficShapingOptions() != null) { json.put("trafficShapingOptions", obj.getTrafficShapingOptions().toJson()); } + json.put("tcpKeepAliveIdleSeconds", obj.getTcpKeepAliveIdleSeconds()); + json.put("tcpKeepAliveCount", obj.getTcpKeepAliveCount()); + json.put("tcpKeepAliveIntervalSeconds", obj.getTcpKeepAliveIntervalSeconds()); json.put("registerWriteHandler", obj.isRegisterWriteHandler()); } } diff --git a/vertx-core/src/main/java/io/vertx/core/net/NetServerOptions.java b/vertx-core/src/main/java/io/vertx/core/net/NetServerOptions.java index 35ffd27dc3f..ffb0246ab12 100755 --- a/vertx-core/src/main/java/io/vertx/core/net/NetServerOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/net/NetServerOptions.java @@ -17,6 +17,7 @@ import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.buffer.Buffer; import io.vertx.core.http.ClientAuth; +import io.vertx.core.impl.Arguments; import io.vertx.core.json.JsonObject; import java.util.Set; @@ -68,6 +69,21 @@ public class NetServerOptions extends TCPSSLOptions { */ public static final boolean DEFAULT_REGISTER_WRITE_HANDLER = false; + /** + * Default value for tcp keepalive idle time -1 defaults to OS settings + */ + public static final int DEFAULT_TCP_KEEPALIVE_IDLE_SECONDS = -1; + + /** + * Default value for tcp keepalive count -1 defaults to OS settings + */ + public static final int DEFAULT_TCP_KEEPALIVE_COUNT = -1; + + /** + * Default value for tcp keepalive interval -1 defaults to OS settings + */ + public static final int DEFAULT_TCP_KEEAPLIVE_INTERVAL_SECONDS = -1; + private int port; private String host; private int acceptBacklog; @@ -76,6 +92,9 @@ public class NetServerOptions extends TCPSSLOptions { private TimeUnit proxyProtocolTimeoutUnit; private boolean registerWriteHandler; private TrafficShapingOptions trafficShapingOptions; + private int tcpKeepAliveIdleSeconds; + private int tcpKeepAliveCount; + private int tcpKeepAliveIntervalSeconds; /** * Default constructor @@ -88,7 +107,7 @@ public NetServerOptions() { /** * Copy constructor * - * @param other the options to copy + * @param other the options to copy */ public NetServerOptions(NetServerOptions other) { super(other); @@ -102,12 +121,15 @@ public NetServerOptions(NetServerOptions other) { DEFAULT_PROXY_PROTOCOL_TIMEOUT_TIME_UNIT; this.registerWriteHandler = other.registerWriteHandler; this.trafficShapingOptions = other.getTrafficShapingOptions(); + this.tcpKeepAliveIdleSeconds = other.getTcpKeepAliveIdleSeconds(); + this.tcpKeepAliveCount = other.getTcpKeepAliveCount(); + this.tcpKeepAliveIntervalSeconds = other.getTcpKeepAliveIntervalSeconds(); } /** * Create some options from JSON * - * @param json the JSON + * @param json the JSON */ public NetServerOptions(JsonObject json) { super(json); @@ -335,7 +357,6 @@ public NetServerOptions setAcceptBacklog(int acceptBacklog) { } /** - * * @return the port */ public int getPort() { @@ -345,7 +366,7 @@ public int getPort() { /** * Set the port * - * @param port the port + * @param port the port * @return a reference to this, so the API can be used fluently */ public NetServerOptions setPort(int port) { @@ -357,7 +378,6 @@ public NetServerOptions setPort(int port) { } /** - * * @return the host */ public String getHost() { @@ -366,7 +386,8 @@ public String getHost() { /** * Set the host - * @param host the host + * + * @param host the host * @return a reference to this, so the API can be used fluently */ public NetServerOptions setHost(String host) { @@ -423,7 +444,9 @@ public NetServerOptions setSni(boolean sni) { /** * @return whether the server uses the HA Proxy protocol */ - public boolean isUseProxyProtocol() { return useProxyProtocol; } + public boolean isUseProxyProtocol() { + return useProxyProtocol; + } /** * Set whether the server uses the HA Proxy protocol @@ -492,6 +515,62 @@ public NetServerOptions setTrafficShapingOptions(TrafficShapingOptions trafficSh return this; } + /** + * @return the time in seconds the connection needs to remain idle before TCP starts sending keepalive probes + */ + public int getTcpKeepAliveIdleSeconds() { + return tcpKeepAliveIdleSeconds; + } + + /** + * The time in seconds the connection needs to remain idle before TCP starts sending keepalive probes, + * if the socket option keepalive has been set. + * + * @param tcpKeepAliveIdleSeconds + * @return a reference to this, so the API can be used fluently + */ + public NetServerOptions setTcpKeepAliveIdleSeconds(int tcpKeepAliveIdleSeconds) { + Arguments.require(tcpKeepAliveIdleSeconds > 0 || tcpKeepAliveIdleSeconds == DEFAULT_TCP_KEEPALIVE_IDLE_SECONDS, "tcpKeepAliveIdleSeconds must be > 0"); + this.tcpKeepAliveIdleSeconds = tcpKeepAliveIdleSeconds; + return this; + } + + /** + * @return the maximum number of keepalive probes TCP should send before dropping the connection. + */ + public int getTcpKeepAliveCount() { + return tcpKeepAliveCount; + } + + /** + * The maximum number of keepalive probes TCP should send before dropping the connection. + * @param tcpKeepAliveCount + * @return a reference to this, so the API can be used fluently + */ + public NetServerOptions setTcpKeepAliveCount(int tcpKeepAliveCount) { + Arguments.require(tcpKeepAliveCount > 0 || tcpKeepAliveCount == DEFAULT_TCP_KEEPALIVE_COUNT, "tcpKeepAliveCount must be > 0"); + this.tcpKeepAliveCount = tcpKeepAliveCount; + return this; + } + + /** + * @return the time in seconds between individual keepalive probes. + */ + public int getTcpKeepAliveIntervalSeconds() { + return tcpKeepAliveIntervalSeconds; + } + + /** + * The time in seconds between individual keepalive probes. + * @param tcpKeepAliveIntervalSeconds + * @return + */ + public NetServerOptions setTcpKeepAliveIntervalSeconds(int tcpKeepAliveIntervalSeconds) { + Arguments.require(tcpKeepAliveIntervalSeconds > 0 || tcpKeepAliveIntervalSeconds == DEFAULT_TCP_KEEAPLIVE_INTERVAL_SECONDS, "tcpKeepAliveIntervalSeconds must be > 0"); + this.tcpKeepAliveIntervalSeconds = tcpKeepAliveIntervalSeconds; + return this; + } + private void init() { this.port = DEFAULT_PORT; this.host = DEFAULT_HOST; @@ -500,6 +579,9 @@ private void init() { this.proxyProtocolTimeout = DEFAULT_PROXY_PROTOCOL_TIMEOUT; this.proxyProtocolTimeoutUnit = DEFAULT_PROXY_PROTOCOL_TIMEOUT_TIME_UNIT; this.registerWriteHandler = DEFAULT_REGISTER_WRITE_HANDLER; + this.tcpKeepAliveIdleSeconds = DEFAULT_TCP_KEEPALIVE_IDLE_SECONDS; + this.tcpKeepAliveCount = DEFAULT_TCP_KEEPALIVE_COUNT; + this.tcpKeepAliveIntervalSeconds = DEFAULT_TCP_KEEAPLIVE_INTERVAL_SECONDS; } /** diff --git a/vertx-core/src/main/java/io/vertx/core/spi/transport/Transport.java b/vertx-core/src/main/java/io/vertx/core/spi/transport/Transport.java index 8eed081b06a..4cfadf2c8ff 100644 --- a/vertx-core/src/main/java/io/vertx/core/spi/transport/Transport.java +++ b/vertx-core/src/main/java/io/vertx/core/spi/transport/Transport.java @@ -14,6 +14,7 @@ import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; +import io.netty.channel.epoll.EpollChannelOption; import io.netty.channel.socket.DatagramChannel; import io.netty.channel.socket.InternetProtocolFamily; import io.vertx.core.datagram.DatagramSocketOptions; @@ -82,11 +83,10 @@ default io.vertx.core.net.SocketAddress convert(SocketAddress address) { IoHandlerFactory ioHandlerFactory(); /** - * @param type one of {@link #ACCEPTOR_EVENT_LOOP_GROUP} or {@link #IO_EVENT_LOOP_GROUP}. - * @param nThreads the number of threads that will be used by this instance. + * @param type one of {@link #ACCEPTOR_EVENT_LOOP_GROUP} or {@link #IO_EVENT_LOOP_GROUP}. + * @param nThreads the number of threads that will be used by this instance. * @param threadFactory the ThreadFactory to use. - * @param ioRatio the IO ratio - * + * @param ioRatio the IO ratio * @return a new event loop group */ default EventLoopGroup eventLoopGroup(int type, int nThreads, ThreadFactory threadFactory, int ioRatio) { @@ -104,14 +104,14 @@ default EventLoopGroup eventLoopGroup(int type, int nThreads, ThreadFactory thre DatagramChannel datagramChannel(InternetProtocolFamily family); /** - * @return the type for channel * @param domainSocket whether to create a unix domain channel or a socket channel + * @return the type for channel */ ChannelFactory channelFactory(boolean domainSocket); /** - * @return the type for server channel * @param domainSocket whether to create a server unix domain channel or a regular server socket channel + * @return the type for server channel */ ChannelFactory serverChannelFactory(boolean domainSocket); @@ -172,6 +172,15 @@ default void configure(NetServerOptions options, boolean domainSocket, ServerBoo if (!domainSocket) { bootstrap.option(ChannelOption.SO_REUSEADDR, options.isReuseAddress()); bootstrap.childOption(ChannelOption.SO_KEEPALIVE, options.isTcpKeepAlive()); + if (options.isTcpKeepAlive() && options.getTcpKeepAliveIdleSeconds() != -1) { + bootstrap.childOption(EpollChannelOption.TCP_KEEPIDLE, options.getTcpKeepAliveIdleSeconds()); + } + if (options.isTcpKeepAlive() && options.getTcpKeepAliveCount() != -1) { + bootstrap.childOption(EpollChannelOption.TCP_KEEPCNT, options.getTcpKeepAliveIdleSeconds()); + } + if (options.isTcpKeepAlive() && options.getTcpKeepAliveIntervalSeconds() != -1) { + bootstrap.childOption(EpollChannelOption.TCP_KEEPINTVL, options.getTcpKeepAliveIdleSeconds()); + } bootstrap.childOption(ChannelOption.TCP_NODELAY, options.isTcpNoDelay()); } if (options.getSendBufferSize() != -1) { diff --git a/vertx-core/src/test/java/io/vertx/tests/net/NetTest.java b/vertx-core/src/test/java/io/vertx/tests/net/NetTest.java index 9152f414836..b89cc85de90 100755 --- a/vertx-core/src/test/java/io/vertx/tests/net/NetTest.java +++ b/vertx-core/src/test/java/io/vertx/tests/net/NetTest.java @@ -398,6 +398,27 @@ public void testServerOptions() { assertEquals(randomProxyTimeout, options.getProxyProtocolTimeout()); assertIllegalArgumentException(() -> options.setProxyProtocolTimeout(-123)); + assertEquals(NetServerOptions.DEFAULT_TCP_KEEPALIVE_IDLE_SECONDS, options.getTcpKeepAliveIdleSeconds()); + rand = TestUtils.randomPositiveInt(); + assertEquals(options, options.setTcpKeepAliveIdleSeconds(rand)); + assertEquals(rand, options.getTcpKeepAliveIdleSeconds()); + assertIllegalArgumentException(() -> options.setTcpKeepAliveIdleSeconds(0)); + assertIllegalArgumentException(() -> options.setTcpKeepAliveIdleSeconds(-123)); + + assertEquals(NetServerOptions.DEFAULT_TCP_KEEPALIVE_COUNT, options.getTcpKeepAliveCount()); + rand = TestUtils.randomPositiveInt(); + assertEquals(options, options.setTcpKeepAliveCount(rand)); + assertEquals(rand, options.getTcpKeepAliveCount()); + assertIllegalArgumentException(() -> options.setTcpKeepAliveCount(0)); + assertIllegalArgumentException(() -> options.setTcpKeepAliveCount(-123)); + + assertEquals(NetServerOptions.DEFAULT_TCP_KEEAPLIVE_INTERVAL_SECONDS, options.getTcpKeepAliveIntervalSeconds()); + rand = TestUtils.randomPositiveInt(); + assertEquals(options, options.setTcpKeepAliveIntervalSeconds(rand)); + assertEquals(rand, options.getTcpKeepAliveIntervalSeconds()); + assertIllegalArgumentException(() -> options.setTcpKeepAliveIntervalSeconds(0)); + assertIllegalArgumentException(() -> options.setTcpKeepAliveIntervalSeconds(-123)); + testComplete(); } @@ -650,6 +671,9 @@ public void testCopyServerOptions() { long sslHandshakeTimeout = TestUtils.randomPositiveLong(); boolean useProxyProtocol = TestUtils.randomBoolean(); long proxyProtocolTimeout = TestUtils.randomPositiveLong(); + int tcpKeepAliveIdleSeconds = TestUtils.randomPositiveInt(); + int tcpKeepAliveCount = TestUtils.randomPositiveInt(); + int tcpKeepAliveIntervalSeconds = TestUtils.randomPositiveInt(); options.setSendBufferSize(sendBufferSize); options.setReceiveBufferSize(receiverBufferSize); @@ -675,6 +699,9 @@ public void testCopyServerOptions() { options.setSslHandshakeTimeout(sslHandshakeTimeout); options.setUseProxyProtocol(useProxyProtocol); options.setProxyProtocolTimeout(proxyProtocolTimeout); + options.setTcpKeepAliveIdleSeconds(tcpKeepAliveIdleSeconds); + options.setTcpKeepAliveCount(tcpKeepAliveCount); + options.setTcpKeepAliveIntervalSeconds(tcpKeepAliveIntervalSeconds); NetServerOptions copy = new NetServerOptions(options); assertEquals(options.toJson(), copy.toJson()); @@ -746,6 +773,9 @@ public void testServerOptionsJson() { long sslHandshakeTimeout = TestUtils.randomPositiveLong(); boolean useProxyProtocol = TestUtils.randomBoolean(); long proxyProtocolTimeout = TestUtils.randomPositiveLong(); + int tcpKeepAliveIdleSeconds = TestUtils.randomPositiveInt(); + int tcpKeepAliveCount = TestUtils.randomPositiveInt(); + int tcpKeepAliveIntervalSeconds = TestUtils.randomPositiveInt(); JsonObject json = new JsonObject(); json.put("sendBufferSize", sendBufferSize) @@ -772,7 +802,10 @@ public void testServerOptionsJson() { .put("sni", sni) .put("sslHandshakeTimeout", sslHandshakeTimeout) .put("useProxyProtocol", useProxyProtocol) - .put("proxyProtocolTimeout", proxyProtocolTimeout); + .put("proxyProtocolTimeout", proxyProtocolTimeout) + .put("tcpKeepAliveIdleSeconds", tcpKeepAliveIdleSeconds) + .put("tcpKeepAliveCount", tcpKeepAliveCount) + .put("tcpKeepAliveIntervalSeconds", tcpKeepAliveIntervalSeconds); NetServerOptions options = new NetServerOptions(json); assertEquals(sendBufferSize, options.getSendBufferSize()); @@ -814,6 +847,9 @@ public void testServerOptionsJson() { assertEquals(sni, options.isSni()); assertEquals(useProxyProtocol, options.isUseProxyProtocol()); assertEquals(proxyProtocolTimeout, options.getProxyProtocolTimeout()); + assertEquals(tcpKeepAliveIdleSeconds, options.getTcpKeepAliveIdleSeconds()); + assertEquals(tcpKeepAliveCount, options.getTcpKeepAliveCount()); + assertEquals(tcpKeepAliveIntervalSeconds, options.getTcpKeepAliveIntervalSeconds()); // Test other keystore/truststore types json.remove("keyStoreOptions"); From 9d048e8f3dc5a81311b8817a2fc0a349182ef909 Mon Sep 17 00:00:00 2001 From: ivu-mawi <150349238+ivu-mawi@users.noreply.github.com> Date: Thu, 12 Feb 2026 18:40:19 +0100 Subject: [PATCH 2/4] EpollTransport: respect extended keepalive parameters TCP_KEEPIDLE, TCP_KEEPCNT and TCP_KEEPINTVL for clients, not just servers --- .../io/vertx/core/impl/transports/EpollTransport.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/vertx-core/src/main/java/io/vertx/core/impl/transports/EpollTransport.java b/vertx-core/src/main/java/io/vertx/core/impl/transports/EpollTransport.java index 77744c12e59..436aa34a8e4 100644 --- a/vertx-core/src/main/java/io/vertx/core/impl/transports/EpollTransport.java +++ b/vertx-core/src/main/java/io/vertx/core/impl/transports/EpollTransport.java @@ -149,6 +149,16 @@ public void configure(ClientOptionsBase options, int connectTimeout, boolean dom bootstrap.option(EpollChannelOption.TCP_USER_TIMEOUT, options.getTcpUserTimeout()); bootstrap.option(EpollChannelOption.TCP_QUICKACK, options.isTcpQuickAck()); bootstrap.option(EpollChannelOption.TCP_CORK, options.isTcpCork()); + + if (options.isTcpKeepAlive() && options.getTcpKeepAliveIdleSeconds() != -1) { + bootstrap.option(EpollChannelOption.TCP_KEEPIDLE, options.getTcpKeepAliveIdleSeconds()); + } + if (options.isTcpKeepAlive() && options.getTcpKeepAliveCount() != -1) { + bootstrap.option(EpollChannelOption.TCP_KEEPCNT, options.getTcpKeepAliveCount()); + } + if (options.isTcpKeepAlive() && options.getTcpKeepAliveIntervalSeconds() != -1) { + bootstrap.option(EpollChannelOption.TCP_KEEPINTVL, options.getTcpKeepAliveIntervalSeconds()); + } } Transport.super.configure(options, connectTimeout, domainSocket, bootstrap); } From 179e583093f0c8713c124ee1819403c1e2c9a889 Mon Sep 17 00:00:00 2001 From: ivu-mawi <150349238+ivu-mawi@users.noreply.github.com> Date: Wed, 29 Apr 2026 17:01:20 +0200 Subject: [PATCH 3/4] Move extended TCP keepalive parameters to TCPSSLOptions, apply in EpollTransport (only) --- .../core/net/NetServerOptionsConverter.java | 18 ---- .../core/net/TCPSSLOptionsConverter.java | 18 ++++ .../core/impl/transports/EpollTransport.java | 10 +++ .../io/vertx/core/net/NetServerOptions.java | 80 ----------------- .../java/io/vertx/core/net/TCPSSLOptions.java | 87 +++++++++++++++++++ .../vertx/core/spi/transport/Transport.java | 9 -- 6 files changed, 115 insertions(+), 107 deletions(-) diff --git a/vertx-core/src/main/generated/io/vertx/core/net/NetServerOptionsConverter.java b/vertx-core/src/main/generated/io/vertx/core/net/NetServerOptionsConverter.java index cf593842d65..7394e8a7f5f 100644 --- a/vertx-core/src/main/generated/io/vertx/core/net/NetServerOptionsConverter.java +++ b/vertx-core/src/main/generated/io/vertx/core/net/NetServerOptionsConverter.java @@ -59,21 +59,6 @@ static void fromJson(Iterable> json, NetServ obj.setTrafficShapingOptions(new io.vertx.core.net.TrafficShapingOptions((io.vertx.core.json.JsonObject)member.getValue())); } break; - case "tcpKeepAliveIdleSeconds": - if (member.getValue() instanceof Number) { - obj.setTcpKeepAliveIdleSeconds(((Number)member.getValue()).intValue()); - } - break; - case "tcpKeepAliveCount": - if (member.getValue() instanceof Number) { - obj.setTcpKeepAliveCount(((Number)member.getValue()).intValue()); - } - break; - case "tcpKeepAliveIntervalSeconds": - if (member.getValue() instanceof Number) { - obj.setTcpKeepAliveIntervalSeconds(((Number)member.getValue()).intValue()); - } - break; case "registerWriteHandler": if (member.getValue() instanceof Boolean) { obj.setRegisterWriteHandler((Boolean)member.getValue()); @@ -105,9 +90,6 @@ static void toJson(NetServerOptions obj, java.util.Map json) { if (obj.getTrafficShapingOptions() != null) { json.put("trafficShapingOptions", obj.getTrafficShapingOptions().toJson()); } - json.put("tcpKeepAliveIdleSeconds", obj.getTcpKeepAliveIdleSeconds()); - json.put("tcpKeepAliveCount", obj.getTcpKeepAliveCount()); - json.put("tcpKeepAliveIntervalSeconds", obj.getTcpKeepAliveIntervalSeconds()); json.put("registerWriteHandler", obj.isRegisterWriteHandler()); } } diff --git a/vertx-core/src/main/generated/io/vertx/core/net/TCPSSLOptionsConverter.java b/vertx-core/src/main/generated/io/vertx/core/net/TCPSSLOptionsConverter.java index 220b7886f37..05b209c1787 100644 --- a/vertx-core/src/main/generated/io/vertx/core/net/TCPSSLOptionsConverter.java +++ b/vertx-core/src/main/generated/io/vertx/core/net/TCPSSLOptionsConverter.java @@ -113,6 +113,21 @@ static void fromJson(Iterable> json, TCPSSLO obj.setTcpUserTimeout(((Number)member.getValue()).intValue()); } break; + case "tcpKeepAliveIdleSeconds": + if (member.getValue() instanceof Number) { + obj.setTcpKeepAliveIdleSeconds(((Number)member.getValue()).intValue()); + } + break; + case "tcpKeepAliveCount": + if (member.getValue() instanceof Number) { + obj.setTcpKeepAliveCount(((Number)member.getValue()).intValue()); + } + break; + case "tcpKeepAliveIntervalSeconds": + if (member.getValue() instanceof Number) { + obj.setTcpKeepAliveIntervalSeconds(((Number)member.getValue()).intValue()); + } + break; case "sslHandshakeTimeout": if (member.getValue() instanceof Number) { obj.setSslHandshakeTimeout(((Number)member.getValue()).longValue()); @@ -167,6 +182,9 @@ static void toJson(TCPSSLOptions obj, java.util.Map json) { json.put("tcpCork", obj.isTcpCork()); json.put("tcpQuickAck", obj.isTcpQuickAck()); json.put("tcpUserTimeout", obj.getTcpUserTimeout()); + json.put("tcpKeepAliveIdleSeconds", obj.getTcpKeepAliveIdleSeconds()); + json.put("tcpKeepAliveCount", obj.getTcpKeepAliveCount()); + json.put("tcpKeepAliveIntervalSeconds", obj.getTcpKeepAliveIntervalSeconds()); json.put("sslHandshakeTimeout", obj.getSslHandshakeTimeout()); if (obj.getSslHandshakeTimeoutUnit() != null) { json.put("sslHandshakeTimeoutUnit", obj.getSslHandshakeTimeoutUnit().name()); diff --git a/vertx-core/src/main/java/io/vertx/core/impl/transports/EpollTransport.java b/vertx-core/src/main/java/io/vertx/core/impl/transports/EpollTransport.java index 436aa34a8e4..4cdf3e4659e 100644 --- a/vertx-core/src/main/java/io/vertx/core/impl/transports/EpollTransport.java +++ b/vertx-core/src/main/java/io/vertx/core/impl/transports/EpollTransport.java @@ -136,6 +136,16 @@ public void configure(NetServerOptions options, boolean domainSocket, ServerBoot bootstrap.childOption(EpollChannelOption.TCP_USER_TIMEOUT, options.getTcpUserTimeout()); bootstrap.childOption(EpollChannelOption.TCP_QUICKACK, options.isTcpQuickAck()); bootstrap.childOption(EpollChannelOption.TCP_CORK, options.isTcpCork()); + + if (options.isTcpKeepAlive() && options.getTcpKeepAliveIdleSeconds() != -1) { + bootstrap.option(EpollChannelOption.TCP_KEEPIDLE, options.getTcpKeepAliveIdleSeconds()); + } + if (options.isTcpKeepAlive() && options.getTcpKeepAliveCount() != -1) { + bootstrap.option(EpollChannelOption.TCP_KEEPCNT, options.getTcpKeepAliveCount()); + } + if (options.isTcpKeepAlive() && options.getTcpKeepAliveIntervalSeconds() != -1) { + bootstrap.option(EpollChannelOption.TCP_KEEPINTVL, options.getTcpKeepAliveIntervalSeconds()); + } } Transport.super.configure(options, domainSocket, bootstrap); } diff --git a/vertx-core/src/main/java/io/vertx/core/net/NetServerOptions.java b/vertx-core/src/main/java/io/vertx/core/net/NetServerOptions.java index ffb0246ab12..19c3c34fb3d 100755 --- a/vertx-core/src/main/java/io/vertx/core/net/NetServerOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/net/NetServerOptions.java @@ -69,21 +69,6 @@ public class NetServerOptions extends TCPSSLOptions { */ public static final boolean DEFAULT_REGISTER_WRITE_HANDLER = false; - /** - * Default value for tcp keepalive idle time -1 defaults to OS settings - */ - public static final int DEFAULT_TCP_KEEPALIVE_IDLE_SECONDS = -1; - - /** - * Default value for tcp keepalive count -1 defaults to OS settings - */ - public static final int DEFAULT_TCP_KEEPALIVE_COUNT = -1; - - /** - * Default value for tcp keepalive interval -1 defaults to OS settings - */ - public static final int DEFAULT_TCP_KEEAPLIVE_INTERVAL_SECONDS = -1; - private int port; private String host; private int acceptBacklog; @@ -92,9 +77,6 @@ public class NetServerOptions extends TCPSSLOptions { private TimeUnit proxyProtocolTimeoutUnit; private boolean registerWriteHandler; private TrafficShapingOptions trafficShapingOptions; - private int tcpKeepAliveIdleSeconds; - private int tcpKeepAliveCount; - private int tcpKeepAliveIntervalSeconds; /** * Default constructor @@ -121,9 +103,6 @@ public NetServerOptions(NetServerOptions other) { DEFAULT_PROXY_PROTOCOL_TIMEOUT_TIME_UNIT; this.registerWriteHandler = other.registerWriteHandler; this.trafficShapingOptions = other.getTrafficShapingOptions(); - this.tcpKeepAliveIdleSeconds = other.getTcpKeepAliveIdleSeconds(); - this.tcpKeepAliveCount = other.getTcpKeepAliveCount(); - this.tcpKeepAliveIntervalSeconds = other.getTcpKeepAliveIntervalSeconds(); } /** @@ -515,62 +494,6 @@ public NetServerOptions setTrafficShapingOptions(TrafficShapingOptions trafficSh return this; } - /** - * @return the time in seconds the connection needs to remain idle before TCP starts sending keepalive probes - */ - public int getTcpKeepAliveIdleSeconds() { - return tcpKeepAliveIdleSeconds; - } - - /** - * The time in seconds the connection needs to remain idle before TCP starts sending keepalive probes, - * if the socket option keepalive has been set. - * - * @param tcpKeepAliveIdleSeconds - * @return a reference to this, so the API can be used fluently - */ - public NetServerOptions setTcpKeepAliveIdleSeconds(int tcpKeepAliveIdleSeconds) { - Arguments.require(tcpKeepAliveIdleSeconds > 0 || tcpKeepAliveIdleSeconds == DEFAULT_TCP_KEEPALIVE_IDLE_SECONDS, "tcpKeepAliveIdleSeconds must be > 0"); - this.tcpKeepAliveIdleSeconds = tcpKeepAliveIdleSeconds; - return this; - } - - /** - * @return the maximum number of keepalive probes TCP should send before dropping the connection. - */ - public int getTcpKeepAliveCount() { - return tcpKeepAliveCount; - } - - /** - * The maximum number of keepalive probes TCP should send before dropping the connection. - * @param tcpKeepAliveCount - * @return a reference to this, so the API can be used fluently - */ - public NetServerOptions setTcpKeepAliveCount(int tcpKeepAliveCount) { - Arguments.require(tcpKeepAliveCount > 0 || tcpKeepAliveCount == DEFAULT_TCP_KEEPALIVE_COUNT, "tcpKeepAliveCount must be > 0"); - this.tcpKeepAliveCount = tcpKeepAliveCount; - return this; - } - - /** - * @return the time in seconds between individual keepalive probes. - */ - public int getTcpKeepAliveIntervalSeconds() { - return tcpKeepAliveIntervalSeconds; - } - - /** - * The time in seconds between individual keepalive probes. - * @param tcpKeepAliveIntervalSeconds - * @return - */ - public NetServerOptions setTcpKeepAliveIntervalSeconds(int tcpKeepAliveIntervalSeconds) { - Arguments.require(tcpKeepAliveIntervalSeconds > 0 || tcpKeepAliveIntervalSeconds == DEFAULT_TCP_KEEAPLIVE_INTERVAL_SECONDS, "tcpKeepAliveIntervalSeconds must be > 0"); - this.tcpKeepAliveIntervalSeconds = tcpKeepAliveIntervalSeconds; - return this; - } - private void init() { this.port = DEFAULT_PORT; this.host = DEFAULT_HOST; @@ -579,9 +502,6 @@ private void init() { this.proxyProtocolTimeout = DEFAULT_PROXY_PROTOCOL_TIMEOUT; this.proxyProtocolTimeoutUnit = DEFAULT_PROXY_PROTOCOL_TIMEOUT_TIME_UNIT; this.registerWriteHandler = DEFAULT_REGISTER_WRITE_HANDLER; - this.tcpKeepAliveIdleSeconds = DEFAULT_TCP_KEEPALIVE_IDLE_SECONDS; - this.tcpKeepAliveCount = DEFAULT_TCP_KEEPALIVE_COUNT; - this.tcpKeepAliveIntervalSeconds = DEFAULT_TCP_KEEAPLIVE_INTERVAL_SECONDS; } /** diff --git a/vertx-core/src/main/java/io/vertx/core/net/TCPSSLOptions.java b/vertx-core/src/main/java/io/vertx/core/net/TCPSSLOptions.java index 10558a5c688..d8195c664bc 100755 --- a/vertx-core/src/main/java/io/vertx/core/net/TCPSSLOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/net/TCPSSLOptions.java @@ -15,6 +15,7 @@ import io.vertx.codegen.annotations.GenIgnore; import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.buffer.Buffer; +import io.vertx.core.impl.Arguments; import io.vertx.core.json.JsonObject; import io.netty.handler.logging.ByteBufFormat; @@ -97,6 +98,27 @@ public abstract class TCPSSLOptions extends NetworkOptions { */ public static final int DEFAULT_TCP_USER_TIMEOUT = 0; + /** + * Default value for tcp keepalive idle time. + *

+ * {@code -1} defaults to OS settings + */ + public static final int DEFAULT_TCP_KEEPALIVE_IDLE_SECONDS = -1; + + /** + * Default value for tcp keepalive count. + *

+ * {@code -1} defaults to OS settings + */ + public static final int DEFAULT_TCP_KEEPALIVE_COUNT = -1; + + /** + * Default value for tcp keepalive interval. + *

+ * {@code -1} defaults to OS settings + */ + public static final int DEFAULT_TCP_KEEAPLIVE_INTERVAL_SECONDS = -1; + private boolean tcpNoDelay; private boolean tcpKeepAlive; private int soLinger; @@ -111,6 +133,9 @@ public abstract class TCPSSLOptions extends NetworkOptions { private boolean tcpCork; private boolean tcpQuickAck; private int tcpUserTimeout; + private int tcpKeepAliveIdleSeconds; + private int tcpKeepAliveCount; + private int tcpKeepAliveIntervalSeconds; private Set enabledCipherSuites; private List crlPaths; @@ -144,6 +169,9 @@ public TCPSSLOptions(TCPSSLOptions other) { this.tcpCork = other.isTcpCork(); this.tcpQuickAck = other.isTcpQuickAck(); this.tcpUserTimeout = other.getTcpUserTimeout(); + this.tcpKeepAliveIdleSeconds = other.getTcpKeepAliveIdleSeconds(); + this.tcpKeepAliveCount = other.getTcpKeepAliveCount(); + this.tcpKeepAliveIntervalSeconds = other.getTcpKeepAliveIntervalSeconds(); SSLOptions sslOptions = other.sslOptions; if (sslOptions != null) { @@ -240,6 +268,9 @@ private void init() { tcpCork = DEFAULT_TCP_CORK; tcpQuickAck = DEFAULT_TCP_QUICKACK; tcpUserTimeout = DEFAULT_TCP_USER_TIMEOUT; + tcpKeepAliveIdleSeconds = DEFAULT_TCP_KEEPALIVE_IDLE_SECONDS; + tcpKeepAliveCount = DEFAULT_TCP_KEEPALIVE_COUNT; + tcpKeepAliveIntervalSeconds = DEFAULT_TCP_KEEAPLIVE_INTERVAL_SECONDS; sslOptions = null; } @@ -704,6 +735,62 @@ public TCPSSLOptions setTcpUserTimeout(int tcpUserTimeout) { return this; } + /** + * @return the time in seconds the connection needs to remain idle before TCP starts sending keepalive probes + */ + public int getTcpKeepAliveIdleSeconds() { + return tcpKeepAliveIdleSeconds; + } + + /** + * The time in seconds the connection needs to remain idle before TCP starts sending keepalive probes, + * if the socket option keepalive has been set. + * + * @param tcpKeepAliveIdleSeconds + * @return a reference to this, so the API can be used fluently + */ + public TCPSSLOptions setTcpKeepAliveIdleSeconds(int tcpKeepAliveIdleSeconds) { + Arguments.require(tcpKeepAliveIdleSeconds > 0 || tcpKeepAliveIdleSeconds == DEFAULT_TCP_KEEPALIVE_IDLE_SECONDS, "tcpKeepAliveIdleSeconds must be > 0"); + this.tcpKeepAliveIdleSeconds = tcpKeepAliveIdleSeconds; + return this; + } + + /** + * @return the maximum number of keepalive probes TCP should send before dropping the connection. + */ + public int getTcpKeepAliveCount() { + return tcpKeepAliveCount; + } + + /** + * The maximum number of keepalive probes TCP should send before dropping the connection. + * @param tcpKeepAliveCount + * @return a reference to this, so the API can be used fluently + */ + public TCPSSLOptions setTcpKeepAliveCount(int tcpKeepAliveCount) { + Arguments.require(tcpKeepAliveCount > 0 || tcpKeepAliveCount == DEFAULT_TCP_KEEPALIVE_COUNT, "tcpKeepAliveCount must be > 0"); + this.tcpKeepAliveCount = tcpKeepAliveCount; + return this; + } + + /** + * @return the time in seconds between individual keepalive probes. + */ + public int getTcpKeepAliveIntervalSeconds() { + return tcpKeepAliveIntervalSeconds; + } + + /** + * The time in seconds between individual keepalive probes. + * @param tcpKeepAliveIntervalSeconds + * @return + */ + public TCPSSLOptions setTcpKeepAliveIntervalSeconds(int tcpKeepAliveIntervalSeconds) { + Arguments.require(tcpKeepAliveIntervalSeconds > 0 || tcpKeepAliveIntervalSeconds == DEFAULT_TCP_KEEAPLIVE_INTERVAL_SECONDS, "tcpKeepAliveIntervalSeconds must be > 0"); + this.tcpKeepAliveIntervalSeconds = tcpKeepAliveIntervalSeconds; + return this; + } + /** * Returns the enabled SSL/TLS protocols * @return the enabled protocols diff --git a/vertx-core/src/main/java/io/vertx/core/spi/transport/Transport.java b/vertx-core/src/main/java/io/vertx/core/spi/transport/Transport.java index 4cfadf2c8ff..6bfe95c388a 100644 --- a/vertx-core/src/main/java/io/vertx/core/spi/transport/Transport.java +++ b/vertx-core/src/main/java/io/vertx/core/spi/transport/Transport.java @@ -172,15 +172,6 @@ default void configure(NetServerOptions options, boolean domainSocket, ServerBoo if (!domainSocket) { bootstrap.option(ChannelOption.SO_REUSEADDR, options.isReuseAddress()); bootstrap.childOption(ChannelOption.SO_KEEPALIVE, options.isTcpKeepAlive()); - if (options.isTcpKeepAlive() && options.getTcpKeepAliveIdleSeconds() != -1) { - bootstrap.childOption(EpollChannelOption.TCP_KEEPIDLE, options.getTcpKeepAliveIdleSeconds()); - } - if (options.isTcpKeepAlive() && options.getTcpKeepAliveCount() != -1) { - bootstrap.childOption(EpollChannelOption.TCP_KEEPCNT, options.getTcpKeepAliveIdleSeconds()); - } - if (options.isTcpKeepAlive() && options.getTcpKeepAliveIntervalSeconds() != -1) { - bootstrap.childOption(EpollChannelOption.TCP_KEEPINTVL, options.getTcpKeepAliveIdleSeconds()); - } bootstrap.childOption(ChannelOption.TCP_NODELAY, options.isTcpNoDelay()); } if (options.getSendBufferSize() != -1) { From 34f1ec575009eaf04ac25dc0e07564fef7dd9e41 Mon Sep 17 00:00:00 2001 From: ivu-mawi <150349238+ivu-mawi@users.noreply.github.com> Date: Thu, 7 May 2026 13:59:08 +0200 Subject: [PATCH 4/4] Also support IoUringTransport, add client options to unit test, don't check whether keepalive is enabled before setting extended options, leave that to the OS --- .../core/impl/transports/EpollTransport.java | 26 +++++-------------- .../impl/transports/IoUringTransport.java | 6 +++++ .../test/java/io/vertx/tests/net/NetTest.java | 21 +++++++++++++++ 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/vertx-core/src/main/java/io/vertx/core/impl/transports/EpollTransport.java b/vertx-core/src/main/java/io/vertx/core/impl/transports/EpollTransport.java index 4cdf3e4659e..b3ae3eaf1f0 100644 --- a/vertx-core/src/main/java/io/vertx/core/impl/transports/EpollTransport.java +++ b/vertx-core/src/main/java/io/vertx/core/impl/transports/EpollTransport.java @@ -136,16 +136,9 @@ public void configure(NetServerOptions options, boolean domainSocket, ServerBoot bootstrap.childOption(EpollChannelOption.TCP_USER_TIMEOUT, options.getTcpUserTimeout()); bootstrap.childOption(EpollChannelOption.TCP_QUICKACK, options.isTcpQuickAck()); bootstrap.childOption(EpollChannelOption.TCP_CORK, options.isTcpCork()); - - if (options.isTcpKeepAlive() && options.getTcpKeepAliveIdleSeconds() != -1) { - bootstrap.option(EpollChannelOption.TCP_KEEPIDLE, options.getTcpKeepAliveIdleSeconds()); - } - if (options.isTcpKeepAlive() && options.getTcpKeepAliveCount() != -1) { - bootstrap.option(EpollChannelOption.TCP_KEEPCNT, options.getTcpKeepAliveCount()); - } - if (options.isTcpKeepAlive() && options.getTcpKeepAliveIntervalSeconds() != -1) { - bootstrap.option(EpollChannelOption.TCP_KEEPINTVL, options.getTcpKeepAliveIntervalSeconds()); - } + bootstrap.childOption(EpollChannelOption.TCP_KEEPIDLE, options.getTcpKeepAliveIdleSeconds()); + bootstrap.childOption(EpollChannelOption.TCP_KEEPCNT, options.getTcpKeepAliveCount()); + bootstrap.childOption(EpollChannelOption.TCP_KEEPINTVL, options.getTcpKeepAliveIntervalSeconds()); } Transport.super.configure(options, domainSocket, bootstrap); } @@ -159,16 +152,9 @@ public void configure(ClientOptionsBase options, int connectTimeout, boolean dom bootstrap.option(EpollChannelOption.TCP_USER_TIMEOUT, options.getTcpUserTimeout()); bootstrap.option(EpollChannelOption.TCP_QUICKACK, options.isTcpQuickAck()); bootstrap.option(EpollChannelOption.TCP_CORK, options.isTcpCork()); - - if (options.isTcpKeepAlive() && options.getTcpKeepAliveIdleSeconds() != -1) { - bootstrap.option(EpollChannelOption.TCP_KEEPIDLE, options.getTcpKeepAliveIdleSeconds()); - } - if (options.isTcpKeepAlive() && options.getTcpKeepAliveCount() != -1) { - bootstrap.option(EpollChannelOption.TCP_KEEPCNT, options.getTcpKeepAliveCount()); - } - if (options.isTcpKeepAlive() && options.getTcpKeepAliveIntervalSeconds() != -1) { - bootstrap.option(EpollChannelOption.TCP_KEEPINTVL, options.getTcpKeepAliveIntervalSeconds()); - } + bootstrap.option(EpollChannelOption.TCP_KEEPIDLE, options.getTcpKeepAliveIdleSeconds()); + bootstrap.option(EpollChannelOption.TCP_KEEPCNT, options.getTcpKeepAliveCount()); + bootstrap.option(EpollChannelOption.TCP_KEEPINTVL, options.getTcpKeepAliveIntervalSeconds()); } Transport.super.configure(options, connectTimeout, domainSocket, bootstrap); } diff --git a/vertx-core/src/main/java/io/vertx/core/impl/transports/IoUringTransport.java b/vertx-core/src/main/java/io/vertx/core/impl/transports/IoUringTransport.java index ff0be012d0f..aab15cf6784 100644 --- a/vertx-core/src/main/java/io/vertx/core/impl/transports/IoUringTransport.java +++ b/vertx-core/src/main/java/io/vertx/core/impl/transports/IoUringTransport.java @@ -141,6 +141,9 @@ public void configure(NetServerOptions options, boolean domainSocket, ServerBoot bootstrap.childOption(IoUringChannelOption.TCP_USER_TIMEOUT, options.getTcpUserTimeout()); bootstrap.childOption(IoUringChannelOption.TCP_QUICKACK, options.isTcpQuickAck()); bootstrap.childOption(IoUringChannelOption.TCP_CORK, options.isTcpCork()); + bootstrap.childOption(IoUringChannelOption.TCP_KEEPIDLE, options.getTcpKeepAliveIdleSeconds()); + bootstrap.childOption(IoUringChannelOption.TCP_KEEPCNT, options.getTcpKeepAliveCount()); + bootstrap.childOption(IoUringChannelOption.TCP_KEEPINTVL, options.getTcpKeepAliveIntervalSeconds()); Transport.super.configure(options, false, bootstrap); } @@ -155,6 +158,9 @@ public void configure(ClientOptionsBase options, int connectTimeout, boolean dom bootstrap.option(IoUringChannelOption.TCP_USER_TIMEOUT, options.getTcpUserTimeout()); bootstrap.option(IoUringChannelOption.TCP_QUICKACK, options.isTcpQuickAck()); bootstrap.option(IoUringChannelOption.TCP_CORK, options.isTcpCork()); + bootstrap.option(IoUringChannelOption.TCP_KEEPIDLE, options.getTcpKeepAliveIdleSeconds()); + bootstrap.option(IoUringChannelOption.TCP_KEEPCNT, options.getTcpKeepAliveCount()); + bootstrap.option(IoUringChannelOption.TCP_KEEPINTVL, options.getTcpKeepAliveIntervalSeconds()); Transport.super.configure(options, connectTimeout, false, bootstrap); } } diff --git a/vertx-core/src/test/java/io/vertx/tests/net/NetTest.java b/vertx-core/src/test/java/io/vertx/tests/net/NetTest.java index b89cc85de90..7bc53a614c2 100755 --- a/vertx-core/src/test/java/io/vertx/tests/net/NetTest.java +++ b/vertx-core/src/test/java/io/vertx/tests/net/NetTest.java @@ -274,6 +274,27 @@ public void testClientOptions() { assertEquals(randLong, options.getSslHandshakeTimeout()); assertIllegalArgumentException(() -> options.setSslHandshakeTimeout(-123)); + assertEquals(NetClientOptions.DEFAULT_TCP_KEEPALIVE_IDLE_SECONDS, options.getTcpKeepAliveIdleSeconds()); + rand = TestUtils.randomPositiveInt(); + assertEquals(options, options.setTcpKeepAliveIdleSeconds(rand)); + assertEquals(rand, options.getTcpKeepAliveIdleSeconds()); + assertIllegalArgumentException(() -> options.setTcpKeepAliveIdleSeconds(0)); + assertIllegalArgumentException(() -> options.setTcpKeepAliveIdleSeconds(-123)); + + assertEquals(NetClientOptions.DEFAULT_TCP_KEEPALIVE_COUNT, options.getTcpKeepAliveCount()); + rand = TestUtils.randomPositiveInt(); + assertEquals(options, options.setTcpKeepAliveCount(rand)); + assertEquals(rand, options.getTcpKeepAliveCount()); + assertIllegalArgumentException(() -> options.setTcpKeepAliveCount(0)); + assertIllegalArgumentException(() -> options.setTcpKeepAliveCount(-123)); + + assertEquals(NetClientOptions.DEFAULT_TCP_KEEAPLIVE_INTERVAL_SECONDS, options.getTcpKeepAliveIntervalSeconds()); + rand = TestUtils.randomPositiveInt(); + assertEquals(options, options.setTcpKeepAliveIntervalSeconds(rand)); + assertEquals(rand, options.getTcpKeepAliveIntervalSeconds()); + assertIllegalArgumentException(() -> options.setTcpKeepAliveIntervalSeconds(0)); + assertIllegalArgumentException(() -> options.setTcpKeepAliveIntervalSeconds(-123)); + testComplete(); }