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

Support TCP_USER_TIMEOUT option #2499

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/main/java/io/lettuce/core/ConnectionBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,18 @@ public void configureBootstrap(boolean domainSocket,
logger.warn("Cannot apply extended TCP keepalive options to channel type " + channelClass.getName());
}
}

if (options.isEnableTcpUserTimeout()) {
SocketOptions.TcpUserTimeoutOptions tcpUserTimeoutOptions = options.getTcpUserTimeout();

if (IOUringProvider.isAvailable()) {
IOUringProvider.applyTcpUserTimeout(bootstrap, tcpUserTimeoutOptions.getTcpUserTimeout());
} else if (io.lettuce.core.resource.EpollProvider.isAvailable()) {
EpollProvider.applyTcpUserTimeout(bootstrap, tcpUserTimeoutOptions.getTcpUserTimeout());
} else {
logger.warn("Cannot apply tcp user timeout options to channel type " + channelClass.getName());
}
}
}

public RedisChannelHandler<?, ?> connection() {
Expand Down
117 changes: 117 additions & 0 deletions src/main/java/io/lettuce/core/SocketOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,16 @@ public class SocketOptions {

public static final boolean DEFAULT_SO_KEEPALIVE = false;

public static final boolean DEFAULT_TCP_USER_TIMEOUT_ENABLED = false;

public static final boolean DEFAULT_SO_NO_DELAY = true;

private final Duration connectTimeout;

private final KeepAliveOptions keepAlive;

private final TcpUserTimeoutOptions tcpUserTimeout;

private final boolean extendedKeepAlive;

private final boolean tcpNoDelay;
Expand All @@ -52,13 +56,15 @@ protected SocketOptions(Builder builder) {
this.keepAlive = builder.keepAlive;
this.extendedKeepAlive = builder.extendedKeepAlive;
this.tcpNoDelay = builder.tcpNoDelay;
this.tcpUserTimeout = builder.tcpUserTimeout;
}

protected SocketOptions(SocketOptions original) {
this.connectTimeout = original.getConnectTimeout();
this.keepAlive = original.getKeepAlive();
this.extendedKeepAlive = original.isExtendedKeepAlive();
this.tcpNoDelay = original.isTcpNoDelay();
this.tcpUserTimeout = original.tcpUserTimeout;
}

/**
Expand Down Expand Up @@ -98,6 +104,9 @@ public static class Builder {

private KeepAliveOptions keepAlive = KeepAliveOptions.builder().enable(DEFAULT_SO_KEEPALIVE).build();

private TcpUserTimeoutOptions tcpUserTimeout = TcpUserTimeoutOptions.builder()
.enable(DEFAULT_TCP_USER_TIMEOUT_ENABLED).build();

private boolean tcpNoDelay = DEFAULT_SO_NO_DELAY;

private boolean extendedKeepAlive = false;
Expand Down Expand Up @@ -174,6 +183,14 @@ public Builder keepAlive(KeepAliveOptions keepAlive) {
return this;
}

public Builder tcpUserTimeout(TcpUserTimeoutOptions tcpUserTimeout) {
LettuceAssert.notNull(tcpUserTimeout, "tcpUserTimeout options must not be null");

this.tcpUserTimeout = tcpUserTimeout;

return this;
}

/**
* Set whether to disable/enable Nagle's algorithm. Defaults to {@code true} (Nagle disabled). See
* {@link #DEFAULT_SO_NO_DELAY}.
Expand Down Expand Up @@ -265,6 +282,14 @@ public boolean isTcpNoDelay() {
return tcpNoDelay;
}

public boolean isEnableTcpUserTimeout() {
return tcpUserTimeout.isEnabled();
}

public TcpUserTimeoutOptions getTcpUserTimeout() {
return tcpUserTimeout;
}

/**
* Extended Keep-Alive options (idle, interval, count). Extended options should not be used in code intended to be portable
* as options are applied only when using NIO sockets with Java 11 or newer epoll sockets, or io_uring sockets. Not
Expand Down Expand Up @@ -483,4 +508,96 @@ public Duration getInterval() {

}

/**
* TCP_USER_TIMEOUT TCP_USER_TIMEOUT comes from <a href="https://datatracker.ietf.org/doc/html/rfc5482">RFC5482</a>
* , configuring this parameter can allow the user TCP to initiate a reconnection to solve this problem when the
* network is abnormal: <a href="https://github.com/lettuce-io/ettuce-core/issues/2082">#2082</a>
*/
public static class TcpUserTimeoutOptions {

/**
* Recommended default TCP_USER_TIMEOUT == TCP_KEEPIDLE(2 hour) + TCP_KEEPINTVL(75 s) * TCP_KEEPCNT(9)
* 2 * 3600 + 75 * 9 = 7875
*/
public static final Duration DEFAULT_TCP_USER_TIMEOUT = Duration.ofSeconds(7875);

private final Duration tcpUserTimeout;

private final boolean enabled;

private TcpUserTimeoutOptions(TcpUserTimeoutOptions.Builder builder) {

this.tcpUserTimeout = builder.tcpUserTimeout;
this.enabled = builder.enabled;
}

public static TcpUserTimeoutOptions.Builder builder() {
return new TcpUserTimeoutOptions.Builder();
}

/**
* Builder class for TcpUserTimeoutOptions.
*/
public static class Builder {

private Duration tcpUserTimeout = DEFAULT_TCP_USER_TIMEOUT;

private boolean enabled = DEFAULT_TCP_USER_TIMEOUT_ENABLED;

private Builder() {
}

public TcpUserTimeoutOptions.Builder enable() {
return enable(true);
}

public TcpUserTimeoutOptions.Builder disable() {
return enable(false);
}

public TcpUserTimeoutOptions.Builder enable(boolean enabled) {

this.enabled = enabled;
return this;
}

public TcpUserTimeoutOptions.Builder tcpUserTimeout(Duration tcpUserTimeout) {

LettuceAssert.notNull(tcpUserTimeout, "tcpUserTimeout must not be null");
LettuceAssert.isTrue(!tcpUserTimeout.isNegative(), "tcpUserTimeout must not be begative");

this.tcpUserTimeout = tcpUserTimeout;
return this;
}

public TcpUserTimeoutOptions build() {
return new TcpUserTimeoutOptions(this);
}

}

/**
* Creates a new Builder instance with the current TcpUserTimeoutOptions state.
*
* @return a new Builder with the current state
*/
public TcpUserTimeoutOptions.Builder mutate() {

TcpUserTimeoutOptions.Builder builder = builder();

builder.enabled = this.isEnabled();
builder.tcpUserTimeout = this.getTcpUserTimeout();

return builder;
}

public boolean isEnabled() {
return enabled;
}

public Duration getTcpUserTimeout() {
return tcpUserTimeout;
}
}

}
7 changes: 7 additions & 0 deletions src/main/java/io/lettuce/core/resource/EpollProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@ public static void applyKeepAlive(Bootstrap bootstrap, int count, Duration idle,
bootstrap.option(EpollChannelOption.TCP_KEEPINTVL, Math.toIntExact(interval.getSeconds()));
}

/**
* Apply TcpUserTimeout options.
*/
public static void applyTcpUserTimeout(Bootstrap bootstrap, Duration timeout) {
bootstrap.option(EpollChannelOption.TCP_USER_TIMEOUT, Math.toIntExact(timeout.toMillis()));
}

/**
* {@link EventLoopResources} for available Epoll.
*/
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/io/lettuce/core/resource/IOUringProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,13 @@ public static void applyKeepAlive(Bootstrap bootstrap, int count, Duration idle,
bootstrap.option(IOUringChannelOption.TCP_KEEPINTVL, Math.toIntExact(interval.getSeconds()));
}

/**
* Apply TcpUserTimeout options.
*/
public static void applyTcpUserTimeout(Bootstrap bootstrap, Duration timeout) {
bootstrap.option(IOUringChannelOption.TCP_USER_TIMEOUT, Math.toIntExact(timeout.toMillis()));
}

/**
* {@link EventLoopResources} for available io_uring.
*/
Expand Down
15 changes: 15 additions & 0 deletions src/test/java/io/lettuce/core/SocketOptionsUnitTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.time.Duration;
import java.util.concurrent.TimeUnit;

import io.lettuce.core.SocketOptions.TcpUserTimeoutOptions;
import org.junit.jupiter.api.Test;

/**
Expand Down Expand Up @@ -88,4 +89,18 @@ void checkAssertions(SocketOptions sut) {
assertThat(sut.isTcpNoDelay()).isTrue();
assertThat(sut.getConnectTimeout()).isEqualTo(Duration.ofSeconds(10));
}

@Test
void testDefaultTcpUserTimeoutOption() {
SocketOptions sut = SocketOptions.builder().build();
assertThat(sut.isEnableTcpUserTimeout()).isFalse();
}

@Test
void testConfigTcpUserTimeoutOption() {
SocketOptions sut = SocketOptions.builder().tcpUserTimeout(TcpUserTimeoutOptions
.builder().enable().tcpUserTimeout(Duration.ofSeconds(60)).build()).build();
assertThat(sut.isEnableTcpUserTimeout()).isTrue();
assertThat(sut.getTcpUserTimeout().getTcpUserTimeout()).isEqualTo(Duration.ofSeconds(60));
}
}