Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Feature 5163 configurable socket keepalive interval #5183

Open
wants to merge 3 commits into
base: 4.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,21 @@ static void fromJson(Iterable<java.util.Map.Entry<String, Object>> json, NetServ
obj.setSni((Boolean)member.getValue());
}
break;
case "tcpKeepAliveCount":
if (member.getValue() instanceof Number) {
obj.setTcpKeepAliveCount(((Number)member.getValue()).intValue());
}
break;
case "tcpKeepAliveIdleSeconds":
if (member.getValue() instanceof Number) {
obj.setTcpKeepAliveIdleSeconds(((Number)member.getValue()).intValue());
}
break;
case "tcpKeepAliveIntervalSeconds":
if (member.getValue() instanceof Number) {
obj.setTcpKeepAliveIntervalSeconds(((Number)member.getValue()).intValue());
}
break;
case "trafficShapingOptions":
if (member.getValue() instanceof JsonObject) {
obj.setTrafficShapingOptions(new io.vertx.core.net.TrafficShapingOptions((io.vertx.core.json.JsonObject)member.getValue()));
Expand Down Expand Up @@ -93,6 +108,9 @@ static void toJson(NetServerOptions obj, java.util.Map<String, Object> json) {
}
json.put("registerWriteHandler", obj.isRegisterWriteHandler());
json.put("sni", obj.isSni());
json.put("tcpKeepAliveCount", obj.getTcpKeepAliveCount());
json.put("tcpKeepAliveIdleSeconds", obj.getTcpKeepAliveIdleSeconds());
json.put("tcpKeepAliveIntervalSeconds", obj.getTcpKeepAliveIntervalSeconds());
if (obj.getTrafficShapingOptions() != null) {
json.put("trafficShapingOptions", obj.getTrafficShapingOptions().toJson());
}
Expand Down
96 changes: 89 additions & 7 deletions src/main/java/io/vertx/core/net/NetServerOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,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;
Expand Down Expand Up @@ -80,6 +81,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;
Expand All @@ -90,6 +106,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
Expand All @@ -102,7 +121,7 @@ public NetServerOptions() {
/**
* Copy constructor
*
* @param other the options to copy
* @param other the options to copy
*/
public NetServerOptions(NetServerOptions other) {
super(other);
Expand All @@ -118,12 +137,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);
Expand Down Expand Up @@ -376,7 +398,6 @@ public NetServerOptions setAcceptBacklog(int acceptBacklog) {
}

/**
*
* @return the port
*/
public int getPort() {
Expand All @@ -386,7 +407,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) {
Expand All @@ -398,7 +419,6 @@ public NetServerOptions setPort(int port) {
}

/**
*
* @return the host
*/
public String getHost() {
Expand All @@ -407,7 +427,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) {
Expand Down Expand Up @@ -462,7 +483,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
Expand Down Expand Up @@ -531,6 +554,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() {
vietj marked this conversation as resolved.
Show resolved Hide resolved
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;
Expand All @@ -541,6 +620,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;
}

/**
Expand Down
21 changes: 15 additions & 6 deletions src/main/java/io/vertx/core/spi/transport/Transport.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -85,11 +86,10 @@ default io.vertx.core.net.SocketAddress convert(SocketAddress address) {
}

/**
* @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
*/
EventLoopGroup eventLoopGroup(int type, int nThreads, ThreadFactory threadFactory, int ioRatio);
Expand All @@ -105,14 +105,14 @@ default io.vertx.core.net.SocketAddress convert(SocketAddress address) {
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<? extends Channel> 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<? extends ServerChannel> serverChannelFactory(boolean domainSocket);

Expand Down Expand Up @@ -174,6 +174,15 @@ default void configure(NetServerOptions options, boolean domainSocket, ServerBoo
bootstrap.option(ChannelOption.SO_REUSEADDR, options.isReuseAddress());
if (!domainSocket) {
bootstrap.childOption(ChannelOption.SO_KEEPALIVE, options.isTcpKeepAlive());
if (options.isTcpKeepAlive() && options.getTcpKeepAliveIdleSeconds() != -1) {
bootstrap.childOption(EpollChannelOption.TCP_KEEPIDLE, options.getTcpKeepAliveIdleSeconds());
vietj marked this conversation as resolved.
Show resolved Hide resolved
}
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) {
Expand Down
38 changes: 37 additions & 1 deletion src/test/java/io/vertx/core/net/NetTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,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();
}

Expand Down Expand Up @@ -638,6 +659,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);
Expand All @@ -662,6 +686,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());
Expand Down Expand Up @@ -731,6 +758,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)
Expand All @@ -756,7 +786,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());
Expand Down Expand Up @@ -797,6 +830,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");
Expand Down