diff --git a/pom.xml b/pom.xml index 6217f430d..d2552a7d4 100644 --- a/pom.xml +++ b/pom.xml @@ -322,7 +322,6 @@ ${source.version} ${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());