diff --git a/pom.xml b/pom.xml
index 6217f430d..d2552a7d4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -322,7 +322,6 @@
${source.version}
- ${release.version}
true
diff --git a/redisson/pom.xml b/redisson/pom.xml
index fe8325620..c0cd7d831 100644
--- a/redisson/pom.xml
+++ b/redisson/pom.xml
@@ -432,6 +432,12 @@
provided
true
+
+ org.apache.tomcat
+ tomcat-annotations-api
+ 9.0.82
+ test
+
diff --git a/redisson/src/main/java/org/redisson/client/RedisClient.java b/redisson/src/main/java/org/redisson/client/RedisClient.java
index 2b0fb55b0..ddfe638fc 100644
--- a/redisson/src/main/java/org/redisson/client/RedisClient.java
+++ b/redisson/src/main/java/org/redisson/client/RedisClient.java
@@ -17,13 +17,20 @@ package org.redisson.client;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
+import io.netty.channel.epoll.EpollChannelOption;
import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.epoll.EpollSocketChannel;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.ChannelGroupFuture;
import io.netty.channel.group.DefaultChannelGroup;
+import io.netty.channel.kqueue.KQueueDatagramChannel;
+import io.netty.channel.kqueue.KQueueSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioChannelOption;
import io.netty.channel.socket.nio.NioDatagramChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.incubator.channel.uring.IOUringChannelOption;
+import io.netty.incubator.channel.uring.IOUringSocketChannel;
import io.netty.resolver.AddressResolver;
import io.netty.resolver.dns.DnsAddressResolverGroup;
import io.netty.resolver.dns.DnsServerAddressStreamProviders;
@@ -32,6 +39,7 @@ import io.netty.util.NetUtil;
import io.netty.util.Timer;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
+import jdk.net.ExtendedSocketOptions;
import org.redisson.api.RFuture;
import org.redisson.client.handler.RedisChannelInitializer;
import org.redisson.client.handler.RedisChannelInitializer.Type;
@@ -40,6 +48,7 @@ import org.redisson.misc.RedisURI;
import java.net.InetAddress;
import java.net.InetSocketAddress;
+import java.net.SocketOption;
import java.net.UnknownHostException;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
@@ -97,6 +106,8 @@ public final class RedisClient {
if (copy.getResolverGroup() == null) {
if (config.getSocketChannelClass() == EpollSocketChannel.class) {
copy.setResolverGroup(new DnsAddressResolverGroup(EpollDatagramChannel.class, DnsServerAddressStreamProviders.platformDefault()));
+ } else if (config.getSocketChannelClass() == KQueueSocketChannel.class) {
+ copy.setResolverGroup(new DnsAddressResolverGroup(KQueueDatagramChannel.class, DnsServerAddressStreamProviders.platformDefault()));
} else {
copy.setResolverGroup(new DnsAddressResolverGroup(NioDatagramChannel.class, DnsServerAddressStreamProviders.platformDefault()));
}
@@ -131,10 +142,64 @@ public final class RedisClient {
bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, config.getConnectTimeout());
bootstrap.option(ChannelOption.SO_KEEPALIVE, config.isKeepAlive());
bootstrap.option(ChannelOption.TCP_NODELAY, config.isTcpNoDelay());
+
+ applyChannelOptions(config, bootstrap);
+
config.getNettyHook().afterBoostrapInitialization(bootstrap);
return bootstrap;
}
+ private void applyChannelOptions(RedisClientConfig config, Bootstrap bootstrap) {
+ if (config.getSocketChannelClass() == EpollSocketChannel.class) {
+ if (config.getTcpKeepAliveCount() > 0) {
+ bootstrap.option(EpollChannelOption.TCP_KEEPCNT, config.getTcpKeepAliveCount());
+ }
+ if (config.getTcpKeepAliveIdle() > 0) {
+ bootstrap.option(EpollChannelOption.TCP_KEEPIDLE, config.getTcpKeepAliveIdle());
+ }
+ if (config.getTcpKeepAliveInterval() > 0) {
+ bootstrap.option(EpollChannelOption.TCP_KEEPINTVL, config.getTcpKeepAliveInterval());
+ }
+ if (config.getTcpUserTimeout() > 0) {
+ bootstrap.option(EpollChannelOption.TCP_USER_TIMEOUT, config.getTcpUserTimeout());
+ }
+ } else if (config.getSocketChannelClass() == IOUringSocketChannel.class) {
+ if (config.getTcpKeepAliveCount() > 0) {
+ bootstrap.option(IOUringChannelOption.TCP_KEEPCNT, config.getTcpKeepAliveCount());
+ }
+ if (config.getTcpKeepAliveIdle() > 0) {
+ bootstrap.option(IOUringChannelOption.TCP_KEEPIDLE, config.getTcpKeepAliveIdle());
+ }
+ if (config.getTcpKeepAliveInterval() > 0) {
+ bootstrap.option(IOUringChannelOption.TCP_KEEPINTVL, config.getTcpKeepAliveInterval());
+ }
+ if (config.getTcpUserTimeout() > 0) {
+ bootstrap.option(IOUringChannelOption.TCP_USER_TIMEOUT, config.getTcpUserTimeout());
+ }
+ } else if (config.getSocketChannelClass() == NioSocketChannel.class) {
+ SocketOption countOption = null;
+ SocketOption idleOption = null;
+ SocketOption intervalOption = null;
+ try {
+ countOption = (SocketOption) ExtendedSocketOptions.class.getDeclaredField("TCP_KEEPCOUNT").get(null);
+ idleOption = (SocketOption) ExtendedSocketOptions.class.getDeclaredField("TCP_KEEPIDLE").get(null);
+ intervalOption = (SocketOption) ExtendedSocketOptions.class.getDeclaredField("TCP_KEEPINTERVAL").get(null);
+ } catch (ReflectiveOperationException e) {
+ // skip
+ }
+
+ if (config.getTcpKeepAliveCount() > 0 && countOption != null) {
+ bootstrap.option(NioChannelOption.of(countOption), config.getTcpKeepAliveCount());
+ }
+ if (config.getTcpKeepAliveIdle() > 0 && idleOption != null) {
+ bootstrap.option(NioChannelOption.of(idleOption), config.getTcpKeepAliveIdle());
+ }
+ if (config.getTcpKeepAliveInterval() > 0 && intervalOption != null) {
+ bootstrap.option(NioChannelOption.of(intervalOption), config.getTcpKeepAliveInterval());
+ }
+ }
+ }
+
public InetSocketAddress getAddr() {
return resolvedAddr;
}
diff --git a/redisson/src/main/java/org/redisson/client/RedisClientConfig.java b/redisson/src/main/java/org/redisson/client/RedisClientConfig.java
index 4e8a6818e..5f3829648 100644
--- a/redisson/src/main/java/org/redisson/client/RedisClientConfig.java
+++ b/redisson/src/main/java/org/redisson/client/RedisClientConfig.java
@@ -59,6 +59,10 @@ public class RedisClientConfig {
private boolean keepPubSubOrder = true;
private int pingConnectionInterval;
private boolean keepAlive;
+ private int tcpKeepAliveCount;
+ private int tcpKeepAliveIdle;
+ private int tcpKeepAliveInterval;
+ private int tcpUserTimeout;
private boolean tcpNoDelay;
private String sslHostname;
@@ -121,6 +125,10 @@ public class RedisClientConfig {
this.sslTrustManagerFactory = config.sslTrustManagerFactory;
this.commandMapper = config.commandMapper;
this.failedNodeDetector = config.failedNodeDetector;
+ this.tcpKeepAliveCount = config.tcpKeepAliveCount;
+ this.tcpKeepAliveIdle = config.tcpKeepAliveIdle;
+ this.tcpKeepAliveInterval = config.tcpKeepAliveInterval;
+ this.tcpUserTimeout = config.tcpUserTimeout;
}
public NettyHook getNettyHook() {
@@ -315,6 +323,39 @@ public class RedisClientConfig {
return this;
}
+ public int getTcpKeepAliveCount() {
+ return tcpKeepAliveCount;
+ }
+ public RedisClientConfig setTcpKeepAliveCount(int tcpKeepAliveCount) {
+ this.tcpKeepAliveCount = tcpKeepAliveCount;
+ return this;
+ }
+
+ public int getTcpKeepAliveIdle() {
+ return tcpKeepAliveIdle;
+ }
+ public RedisClientConfig setTcpKeepAliveIdle(int tcpKeepAliveIdle) {
+ this.tcpKeepAliveIdle = tcpKeepAliveIdle;
+ return this;
+ }
+
+ public int getTcpKeepAliveInterval() {
+ return tcpKeepAliveInterval;
+ }
+ public RedisClientConfig setTcpKeepAliveInterval(int tcpKeepAliveInterval) {
+ this.tcpKeepAliveInterval = tcpKeepAliveInterval;
+ return this;
+ }
+
+ public int getTcpUserTimeout() {
+ return tcpUserTimeout;
+ }
+
+ public RedisClientConfig setTcpUserTimeout(int tcpUserTimeout) {
+ this.tcpUserTimeout = tcpUserTimeout;
+ return this;
+ }
+
public boolean isTcpNoDelay() {
return tcpNoDelay;
}
diff --git a/redisson/src/main/java/org/redisson/config/BaseConfig.java b/redisson/src/main/java/org/redisson/config/BaseConfig.java
index c36a9383e..605714895 100644
--- a/redisson/src/main/java/org/redisson/config/BaseConfig.java
+++ b/redisson/src/main/java/org/redisson/config/BaseConfig.java
@@ -103,7 +103,15 @@ public class BaseConfig> {
private int pingConnectionInterval = 30000;
private boolean keepAlive;
-
+
+ private int tcpKeepAliveCount;
+
+ private int tcpKeepAliveIdle;
+
+ private int tcpKeepAliveInterval;
+
+ private int tcpUserTimeout;
+
private boolean tcpNoDelay = true;
private NameMapper nameMapper = NameMapper.direct();
@@ -135,6 +143,10 @@ public class BaseConfig> {
setSslTrustManagerFactory(config.getSslTrustManagerFactory());
setPingConnectionInterval(config.getPingConnectionInterval());
setKeepAlive(config.isKeepAlive());
+ setTcpKeepAliveCount(config.getTcpKeepAliveCount());
+ setTcpKeepAliveIdle(config.getTcpKeepAliveIdle());
+ setTcpKeepAliveInterval(config.getTcpKeepAliveInterval());
+ setTcpUserTimeout(config.getTcpUserTimeout());
setTcpNoDelay(config.isTcpNoDelay());
setNameMapper(config.getNameMapper());
setCredentialsResolver(config.getCredentialsResolver());
@@ -459,6 +471,70 @@ public class BaseConfig> {
return (T) this;
}
+ public int getTcpKeepAliveCount() {
+ return tcpKeepAliveCount;
+ }
+
+ /**
+ * Defines the maximum number of keepalive probes
+ * TCP should send before dropping the connection.
+ *
+ * @param tcpKeepAliveCount maximum number of keepalive probes
+ * @return config
+ */
+ public T setTcpKeepAliveCount(int tcpKeepAliveCount) {
+ this.tcpKeepAliveCount = tcpKeepAliveCount;
+ return (T) this;
+ }
+
+ public int getTcpKeepAliveIdle() {
+ return tcpKeepAliveIdle;
+ }
+
+ /**
+ * Defines the time in seconds the connection needs to remain idle
+ * before TCP starts sending keepalive probes,
+ *
+ * @param tcpKeepAliveIdle time in seconds
+ * @return config
+ */
+ public T setTcpKeepAliveIdle(int tcpKeepAliveIdle) {
+ this.tcpKeepAliveIdle = tcpKeepAliveIdle;
+ return (T) this;
+ }
+
+ public int getTcpKeepAliveInterval() {
+ return tcpKeepAliveInterval;
+ }
+
+ /**
+ * Defines the time in seconds between individual keepalive probes.
+ *
+ * @param tcpKeepAliveInterval time in seconds
+ * @return config
+ */
+ public T setTcpKeepAliveInterval(int tcpKeepAliveInterval) {
+ this.tcpKeepAliveInterval = tcpKeepAliveInterval;
+ return (T) this;
+ }
+
+ public int getTcpUserTimeout() {
+ return tcpUserTimeout;
+ }
+
+ /**
+ * Defines the maximum amount of time in milliseconds that transmitted data may
+ * remain unacknowledged, or buffered data may remain untransmitted
+ * (due to zero window size) before TCP will forcibly close the connection.
+ *
+ * @param tcpUserTimeout time in milliseconds
+ * @return config
+ */
+ public T setTcpUserTimeout(int tcpUserTimeout) {
+ this.tcpUserTimeout = tcpUserTimeout;
+ return (T) this;
+ }
+
public boolean isTcpNoDelay() {
return tcpNoDelay;
}
diff --git a/redisson/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java b/redisson/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java
index 6290aa8f9..02c03d97d 100644
--- a/redisson/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java
+++ b/redisson/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java
@@ -291,6 +291,10 @@ public class MasterSlaveConnectionManager implements ConnectionManager {
c.setSubscriptionMode(cfg.getSubscriptionMode());
c.setDnsMonitoringInterval(cfg.getDnsMonitoringInterval());
c.setKeepAlive(cfg.isKeepAlive());
+ c.setTcpKeepAliveCount(cfg.getTcpKeepAliveCount());
+ c.setTcpKeepAliveIdle(cfg.getTcpKeepAliveIdle());
+ c.setTcpKeepAliveInterval(cfg.getTcpKeepAliveInterval());
+ c.setTcpUserTimeout(cfg.getTcpUserTimeout());
c.setTcpNoDelay(cfg.isTcpNoDelay());
c.setNameMapper(cfg.getNameMapper());
c.setCredentialsResolver(cfg.getCredentialsResolver());
@@ -347,6 +351,10 @@ public class MasterSlaveConnectionManager implements ConnectionManager {
.setKeepPubSubOrder(serviceManager.getCfg().isKeepPubSubOrder())
.setPingConnectionInterval(config.getPingConnectionInterval())
.setKeepAlive(config.isKeepAlive())
+ .setTcpKeepAliveCount(config.getTcpKeepAliveCount())
+ .setTcpKeepAliveIdle(config.getTcpKeepAliveIdle())
+ .setTcpKeepAliveInterval(config.getTcpKeepAliveInterval())
+ .setTcpUserTimeout(config.getTcpUserTimeout())
.setTcpNoDelay(config.isTcpNoDelay())
.setUsername(config.getUsername())
.setPassword(config.getPassword())
diff --git a/redisson/src/main/java/org/redisson/connection/SingleConnectionManager.java b/redisson/src/main/java/org/redisson/connection/SingleConnectionManager.java
index 8afa1fe4d..e20dc093f 100644
--- a/redisson/src/main/java/org/redisson/connection/SingleConnectionManager.java
+++ b/redisson/src/main/java/org/redisson/connection/SingleConnectionManager.java
@@ -66,6 +66,10 @@ public class SingleConnectionManager extends MasterSlaveConnectionManager {
newconfig.setReadMode(ReadMode.MASTER);
newconfig.setSubscriptionMode(SubscriptionMode.MASTER);
newconfig.setKeepAlive(cfg.isKeepAlive());
+ newconfig.setTcpKeepAliveCount(cfg.getTcpKeepAliveCount());
+ newconfig.setTcpKeepAliveIdle(cfg.getTcpKeepAliveIdle());
+ newconfig.setTcpKeepAliveInterval(cfg.getTcpKeepAliveInterval());
+ newconfig.setTcpUserTimeout(cfg.getTcpUserTimeout());
newconfig.setTcpNoDelay(cfg.isTcpNoDelay());
newconfig.setNameMapper(cfg.getNameMapper());
newconfig.setCredentialsResolver(cfg.getCredentialsResolver());