From d9f19919a9022c976c249a57a0b03512503f1a29 Mon Sep 17 00:00:00 2001 From: Shashank Bairy R Date: Wed, 28 Jan 2026 00:08:35 +0530 Subject: [PATCH 1/3] fx fluent api pattern and server bootstrapping for tcp user timeout --- .../src/main/java/io/vertx/core/http/HttpServerOptions.java | 5 +++++ .../java/io/vertx/core/impl/transports/EpollTransport.java | 1 + .../src/main/java/io/vertx/core/net/NetServerOptions.java | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/vertx-core/src/main/java/io/vertx/core/http/HttpServerOptions.java b/vertx-core/src/main/java/io/vertx/core/http/HttpServerOptions.java index e2b8899b707..0cb1a955ae1 100755 --- a/vertx-core/src/main/java/io/vertx/core/http/HttpServerOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/http/HttpServerOptions.java @@ -458,6 +458,11 @@ public HttpServerOptions setTcpQuickAck(boolean tcpQuickAck) { return (HttpServerOptions) super.setTcpQuickAck(tcpQuickAck); } + @Override + public HttpServerOptions setTcpUserTimeout(int tcpUserTimeout) { + return (HttpServerOptions) super.setTcpUserTimeout(tcpUserTimeout); + } + @Override public HttpServerOptions addCrlPath(String crlPath) throws NullPointerException { return (HttpServerOptions) super.addCrlPath(crlPath); 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 8db16e8481a..6e6cd9cdb6f 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 @@ -132,6 +132,7 @@ public void configure(TcpConfig options, boolean domainSocket, ServerBootstrap b if (options.isTcpFastOpen()) { bootstrap.option(ChannelOption.TCP_FASTOPEN, options.isTcpFastOpen() ? pendingFastOpenRequestsThreshold : 0); } + bootstrap.childOption(EpollChannelOption.TCP_USER_TIMEOUT, options.getTcpUserTimeout()); bootstrap.childOption(EpollChannelOption.TCP_QUICKACK, options.isTcpQuickAck()); bootstrap.childOption(EpollChannelOption.TCP_CORK, options.isTcpCork()); } 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 9a6d83c06be..6f2f7495532 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 @@ -291,6 +291,11 @@ public NetServerOptions setTcpQuickAck(boolean tcpQuickAck) { return (NetServerOptions) super.setTcpQuickAck(tcpQuickAck); } + @Override + public NetServerOptions setTcpUserTimeout(int tcpUserTimeout) { + return (NetServerOptions) super.setTcpUserTimeout(tcpUserTimeout); + } + @Override public NetServerOptions addCrlPath(String crlPath) throws NullPointerException { return (NetServerOptions) super.addCrlPath(crlPath); From d4c1bcea86f281c02d710d5f4c0c5a911d7d8f8d Mon Sep 17 00:00:00 2001 From: Shashank Bairy R Date: Mon, 2 Feb 2026 10:02:21 +0530 Subject: [PATCH 2/3] fx tcp user timeout for iouring and tests --- .../src/main/java/examples/CoreExamples.java | 3 ++- .../impl/transports/IoUringTransport.java | 2 ++ .../io/vertx/core/net/NetClientOptions.java | 5 ++++ .../java/io/vertx/core/net/TCPSSLOptions.java | 3 +++ .../java/io/vertx/tests/http/Http1xTest.java | 18 ++++++++++++++ .../test/java/io/vertx/tests/net/NetTest.java | 24 +++++++++++++++++++ 6 files changed, 54 insertions(+), 1 deletion(-) diff --git a/vertx-core/src/main/java/examples/CoreExamples.java b/vertx-core/src/main/java/examples/CoreExamples.java index 7ce9403e58a..025f3735fa9 100644 --- a/vertx-core/src/main/java/examples/CoreExamples.java +++ b/vertx-core/src/main/java/examples/CoreExamples.java @@ -501,13 +501,14 @@ public void configureTransport() { .build(); } - public void configureLinuxOptions(Vertx vertx, boolean fastOpen, boolean cork, boolean quickAck, boolean reusePort) { + public void configureLinuxOptions(Vertx vertx, boolean fastOpen, boolean cork, boolean quickAck, boolean reusePort, int tcpUserTimeout) { // Available on Linux vertx.createHttpServer(new HttpServerOptions() .setTcpFastOpen(fastOpen) .setTcpCork(cork) .setTcpQuickAck(quickAck) .setReusePort(reusePort) + .setTcpUserTimeout(tcpUserTimeout) ); } 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 9aaae65bd3b..b8291869f2e 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 @@ -137,6 +137,7 @@ public void configure(TcpConfig options, boolean domainSocket, ServerBootstrap b if (options.isTcpFastOpen()) { bootstrap.option(IoUringChannelOption.TCP_FASTOPEN, options.isTcpFastOpen() ? pendingFastOpenRequestsThreshold : 0); } + bootstrap.childOption(IoUringChannelOption.TCP_USER_TIMEOUT, options.getTcpUserTimeout()); bootstrap.childOption(IoUringChannelOption.TCP_QUICKACK, options.isTcpQuickAck()); bootstrap.childOption(IoUringChannelOption.TCP_CORK, options.isTcpCork()); Transport.super.configure(options, false, bootstrap); @@ -150,6 +151,7 @@ public void configure(TcpConfig options, boolean domainSocket, Bootstrap bootstr if (options.isTcpFastOpen()) { bootstrap.option(IoUringChannelOption.TCP_FASTOPEN_CONNECT, options.isTcpFastOpen()); } + bootstrap.option(IoUringChannelOption.TCP_USER_TIMEOUT, options.getTcpUserTimeout()); bootstrap.option(IoUringChannelOption.TCP_QUICKACK, options.isTcpQuickAck()); bootstrap.option(IoUringChannelOption.TCP_CORK, options.isTcpCork()); Transport.super.configure(options, false, bootstrap); diff --git a/vertx-core/src/main/java/io/vertx/core/net/NetClientOptions.java b/vertx-core/src/main/java/io/vertx/core/net/NetClientOptions.java index 49970297cba..1ee8c7b78d5 100755 --- a/vertx-core/src/main/java/io/vertx/core/net/NetClientOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/net/NetClientOptions.java @@ -240,6 +240,11 @@ public NetClientOptions setTcpQuickAck(boolean tcpQuickAck) { return (NetClientOptions) super.setTcpQuickAck(tcpQuickAck); } + @Override + public NetClientOptions setTcpUserTimeout(int tcpUserTimeout) { + return (NetClientOptions) super.setTcpUserTimeout(tcpUserTimeout); + } + @Override public NetClientOptions addCrlPath(String crlPath) throws NullPointerException { return (NetClientOptions) super.addCrlPath(crlPath); 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 92e3457bfe0..6eb061973b9 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 @@ -742,6 +742,9 @@ public int getTcpUserTimeout() { * @param tcpUserTimeout the tcp user timeout value */ public TCPSSLOptions setTcpUserTimeout(int tcpUserTimeout) { + if (tcpUserTimeout < 0) { + throw new IllegalArgumentException("tcpUserTimeout must be >= 0"); + } transportOptions.setTcpUserTimeout(tcpUserTimeout); return this; } diff --git a/vertx-core/src/test/java/io/vertx/tests/http/Http1xTest.java b/vertx-core/src/test/java/io/vertx/tests/http/Http1xTest.java index 45985a6dbf7..8b92b0eb461 100644 --- a/vertx-core/src/test/java/io/vertx/tests/http/Http1xTest.java +++ b/vertx-core/src/test/java/io/vertx/tests/http/Http1xTest.java @@ -120,6 +120,12 @@ public void testClientOptions() { assertEquals(options, options.setTcpKeepAlive(!tcpKeepAlive)); assertEquals(!tcpKeepAlive, options.isTcpKeepAlive()); + assertEquals(TCPSSLOptions.DEFAULT_TCP_USER_TIMEOUT, options.getTcpUserTimeout()); + int tcpUserTimeout = TestUtils.randomPositiveInt(); + assertEquals(options, options.setTcpUserTimeout(tcpUserTimeout)); + assertEquals(tcpUserTimeout, options.getTcpUserTimeout()); + assertIllegalArgumentException(() -> options.setTcpUserTimeout(-1000)); + int soLinger = -1; assertEquals(soLinger, options.getSoLinger()); rand = TestUtils.randomPositiveInt(); @@ -309,6 +315,12 @@ public void testServerOptions() { assertEquals(options, options.setTcpKeepAlive(!tcpKeepAlive)); assertEquals(!tcpKeepAlive, options.isTcpKeepAlive()); + assertEquals(TCPSSLOptions.DEFAULT_TCP_USER_TIMEOUT, options.getTcpUserTimeout()); + int tcpUserTimeout = TestUtils.randomPositiveInt(); + assertEquals(options, options.setTcpUserTimeout(tcpUserTimeout)); + assertEquals(tcpUserTimeout, options.getTcpUserTimeout()); + assertIllegalArgumentException(() -> options.setTcpUserTimeout(-1000)); + int soLinger = -1; assertEquals(soLinger, options.getSoLinger()); rand = TestUtils.randomPositiveInt(); @@ -569,6 +581,7 @@ public void testClientOptionsJson() { int trafficClass = TestUtils.randomByte() + 128; boolean tcpNoDelay = rand.nextBoolean(); boolean tcpKeepAlive = rand.nextBoolean(); + int tcpUserTimeout = TestUtils.randomPositiveInt(); int soLinger = TestUtils.randomPositiveInt(); int idleTimeout = TestUtils.randomPositiveInt(); boolean ssl = rand.nextBoolean(); @@ -619,6 +632,7 @@ public void testClientOptionsJson() { .put("trafficClass", trafficClass) .put("tcpNoDelay", tcpNoDelay) .put("tcpKeepAlive", tcpKeepAlive) + .put("tcpUserTimeout", tcpUserTimeout) .put("soLinger", soLinger) .put("idleTimeout", idleTimeout) .put("ssl", ssl) @@ -666,6 +680,7 @@ public void testClientOptionsJson() { assertEquals(reuseAddress, options.isReuseAddress()); assertEquals(trafficClass, options.getTrafficClass()); assertEquals(tcpKeepAlive, options.isTcpKeepAlive()); + assertEquals(tcpUserTimeout, options.getTcpUserTimeout()); assertEquals(tcpNoDelay, options.isTcpNoDelay()); assertEquals(soLinger, options.getSoLinger()); assertEquals(idleTimeout, options.getIdleTimeout()); @@ -871,6 +886,7 @@ public void testServerOptionsJson() { int trafficClass = TestUtils.randomByte() + 128; boolean tcpNoDelay = rand.nextBoolean(); boolean tcpKeepAlive = rand.nextBoolean(); + int tcpUserTimeout = TestUtils.randomPositiveInt(); int soLinger = TestUtils.randomPositiveInt(); int idleTimeout = TestUtils.randomPositiveInt(); boolean ssl = rand.nextBoolean(); @@ -914,6 +930,7 @@ public void testServerOptionsJson() { .put("trafficClass", trafficClass) .put("tcpNoDelay", tcpNoDelay) .put("tcpKeepAlive", tcpKeepAlive) + .put("tcpUserTimeout", tcpUserTimeout) .put("soLinger", soLinger) .put("idleTimeout", idleTimeout) .put("ssl", ssl) @@ -954,6 +971,7 @@ public void testServerOptionsJson() { assertEquals(reuseAddress, options.isReuseAddress()); assertEquals(trafficClass, options.getTrafficClass()); assertEquals(tcpKeepAlive, options.isTcpKeepAlive()); + assertEquals(tcpUserTimeout, options.getTcpUserTimeout()); assertEquals(tcpNoDelay, options.isTcpNoDelay()); assertEquals(soLinger, options.getSoLinger()); assertEquals(idleTimeout, options.getIdleTimeout()); 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 25e55b77f2f..1da75c1c67e 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 @@ -170,6 +170,12 @@ public void testClientOptions() { assertEquals(options, options.setTcpKeepAlive(!tcpKeepAlive)); assertEquals(!tcpKeepAlive, options.isTcpKeepAlive()); + assertEquals(TCPSSLOptions.DEFAULT_TCP_USER_TIMEOUT, options.getTcpUserTimeout()); + int tcpUserTimeout = TestUtils.randomPositiveInt(); + assertEquals(options, options.setTcpUserTimeout(tcpUserTimeout)); + assertEquals(tcpUserTimeout, options.getTcpUserTimeout()); + assertIllegalArgumentException(() -> options.setTcpUserTimeout(-1000)); + int soLinger = -1; assertEquals(soLinger, options.getSoLinger()); rand = TestUtils.randomPositiveInt(); @@ -279,6 +285,12 @@ public void testServerOptions() { assertEquals(options, options.setTcpKeepAlive(!tcpKeepAlive)); assertEquals(!tcpKeepAlive, options.isTcpKeepAlive()); + assertEquals(TCPSSLOptions.DEFAULT_TCP_USER_TIMEOUT, options.getTcpUserTimeout()); + int tcpUserTimeout = TestUtils.randomPositiveInt(); + assertEquals(options, options.setTcpUserTimeout(tcpUserTimeout)); + assertEquals(tcpUserTimeout, options.getTcpUserTimeout()); + assertIllegalArgumentException(() -> options.setTcpUserTimeout(-1000)); + int soLinger = -1; assertEquals(soLinger, options.getSoLinger()); rand = TestUtils.randomPositiveInt(); @@ -369,6 +381,7 @@ public void testCopyClientOptions() { int trafficClass = TestUtils.randomByte() + 128; boolean tcpNoDelay = rand.nextBoolean(); boolean tcpKeepAlive = rand.nextBoolean(); + int tcpUserTimeout = TestUtils.randomPositiveInt(); int soLinger = TestUtils.randomPositiveInt(); int idleTimeout = TestUtils.randomPositiveInt(); boolean ssl = rand.nextBoolean(); @@ -398,6 +411,7 @@ public void testCopyClientOptions() { options.setSsl(ssl); options.setTcpNoDelay(tcpNoDelay); options.setTcpKeepAlive(tcpKeepAlive); + options.setTcpUserTimeout(tcpUserTimeout); options.setSoLinger(soLinger); options.setIdleTimeout(idleTimeout); options.setKeyCertOptions(keyStoreOptions); @@ -430,6 +444,7 @@ public void testDefaultClientOptionsJson() { assertEquals(def.getConnectTimeout(), json.getConnectTimeout()); assertEquals(def.isTcpNoDelay(), json.isTcpNoDelay()); assertEquals(def.isTcpKeepAlive(), json.isTcpKeepAlive()); + assertEquals(def.getTcpUserTimeout(), json.getTcpUserTimeout()); assertEquals(def.getSoLinger(), json.getSoLinger()); assertEquals(def.isSsl(), json.isSsl()); assertEquals(def.isUseAlpn(), json.isUseAlpn()); @@ -447,6 +462,7 @@ public void testClientOptionsJson() { int trafficClass = TestUtils.randomByte() + 128; boolean tcpNoDelay = rand.nextBoolean(); boolean tcpKeepAlive = rand.nextBoolean(); + int tcpUserTimeout = TestUtils.randomPositiveInt(); int soLinger = TestUtils.randomPositiveInt(); int idleTimeout = TestUtils.randomPositiveInt(); boolean ssl = rand.nextBoolean(); @@ -489,6 +505,7 @@ public void testClientOptionsJson() { .put("trafficClass", trafficClass) .put("tcpNoDelay", tcpNoDelay) .put("tcpKeepAlive", tcpKeepAlive) + .put("tcpUserTimeout", tcpUserTimeout) .put("soLinger", soLinger) .put("idleTimeout", idleTimeout) .put("ssl", ssl) @@ -517,6 +534,7 @@ public void testClientOptionsJson() { assertEquals(reuseAddress, options.isReuseAddress()); assertEquals(trafficClass, options.getTrafficClass()); assertEquals(tcpKeepAlive, options.isTcpKeepAlive()); + assertEquals(tcpUserTimeout, options.getTcpUserTimeout()); assertEquals(tcpNoDelay, options.isTcpNoDelay()); assertEquals(soLinger, options.getSoLinger()); assertEquals(idleTimeout, options.getIdleTimeout()); @@ -578,6 +596,7 @@ public void testCopyServerOptions() { int trafficClass = TestUtils.randomByte() + 128; boolean tcpNoDelay = rand.nextBoolean(); boolean tcpKeepAlive = rand.nextBoolean(); + int tcpUserTimeout = TestUtils.randomPositiveInt(); int soLinger = TestUtils.randomPositiveInt(); boolean usePooledBuffers = rand.nextBoolean(); int idleTimeout = TestUtils.randomPositiveInt(); @@ -608,6 +627,7 @@ public void testCopyServerOptions() { options.setTrafficClass(trafficClass); options.setTcpNoDelay(tcpNoDelay); options.setTcpKeepAlive(tcpKeepAlive); + options.setTcpUserTimeout(tcpUserTimeout); options.setSoLinger(soLinger); options.setIdleTimeout(idleTimeout); options.setSsl(ssl); @@ -647,6 +667,7 @@ public void testDefaultServerOptionsJson() { assertEquals(def.getHost(), json.getHost()); assertEquals(def.isTcpNoDelay(), json.isTcpNoDelay()); assertEquals(def.isTcpKeepAlive(), json.isTcpKeepAlive()); + assertEquals(def.getTcpUserTimeout(), json.getTcpUserTimeout()); assertEquals(def.getSoLinger(), json.getSoLinger()); assertEquals(def.isSsl(), json.isSsl()); assertEquals(def.isUseAlpn(), json.isUseAlpn()); @@ -668,6 +689,7 @@ public void testServerOptionsJson() { int trafficClass = TestUtils.randomByte() + 128; boolean tcpNoDelay = rand.nextBoolean(); boolean tcpKeepAlive = rand.nextBoolean(); + int tcpUserTimeout = TestUtils.randomPositiveInt(); int soLinger = TestUtils.randomPositiveInt(); boolean usePooledBuffers = rand.nextBoolean(); int idleTimeout = TestUtils.randomPositiveInt(); @@ -702,6 +724,7 @@ public void testServerOptionsJson() { .put("trafficClass", trafficClass) .put("tcpNoDelay", tcpNoDelay) .put("tcpKeepAlive", tcpKeepAlive) + .put("tcpUserTimeout", tcpUserTimeout) .put("soLinger", soLinger) .put("usePooledBuffers", usePooledBuffers) .put("idleTimeout", idleTimeout) @@ -727,6 +750,7 @@ public void testServerOptionsJson() { assertEquals(reuseAddress, options.isReuseAddress()); assertEquals(trafficClass, options.getTrafficClass()); assertEquals(tcpKeepAlive, options.isTcpKeepAlive()); + assertEquals(tcpUserTimeout, options.getTcpUserTimeout()); assertEquals(tcpNoDelay, options.isTcpNoDelay()); assertEquals(soLinger, options.getSoLinger()); assertEquals(idleTimeout, options.getIdleTimeout()); From d43a8b8f6955e1cc109840b624c0990367350e8e Mon Sep 17 00:00:00 2001 From: Shashank Bairy R Date: Fri, 6 Feb 2026 19:27:06 +0530 Subject: [PATCH 3/3] move user timeout validation to tcp config --- vertx-core/src/main/java/io/vertx/core/net/TCPSSLOptions.java | 3 --- vertx-core/src/main/java/io/vertx/core/net/TcpConfig.java | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) 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 6eb061973b9..92e3457bfe0 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 @@ -742,9 +742,6 @@ public int getTcpUserTimeout() { * @param tcpUserTimeout the tcp user timeout value */ public TCPSSLOptions setTcpUserTimeout(int tcpUserTimeout) { - if (tcpUserTimeout < 0) { - throw new IllegalArgumentException("tcpUserTimeout must be >= 0"); - } transportOptions.setTcpUserTimeout(tcpUserTimeout); return this; } diff --git a/vertx-core/src/main/java/io/vertx/core/net/TcpConfig.java b/vertx-core/src/main/java/io/vertx/core/net/TcpConfig.java index efe642e1fcb..5074e9ddab4 100644 --- a/vertx-core/src/main/java/io/vertx/core/net/TcpConfig.java +++ b/vertx-core/src/main/java/io/vertx/core/net/TcpConfig.java @@ -293,6 +293,9 @@ public int getTcpUserTimeout() { * @param tcpUserTimeout the tcp user timeout value */ public TcpConfig setTcpUserTimeout(int tcpUserTimeout) { + if (tcpUserTimeout < 0) { + throw new IllegalArgumentException("tcpUserTimeout must be >= 0"); + } this.tcpUserTimeout = tcpUserTimeout; return this; }