Repository: netty/netty
Issue: Introduce ability to set TCP_USER_TIMEOUT in transport-native-epoll
Labels: feature

Context:

Netty 4.0.31.Final-SNAPSHOT

I am using ChannelOption.SO_KEEPALIVE together with EpollChannelOption.TCP_KEEP\* in order to detect connection problems. Works as expected however when I keep writing to that connection e.g. every second, the connection will not become idle and will not be torn down by the OS.
I've modified transport-native-epoll to add setting for TCP_USER_TIMEOUT  which allows connections to "fail fast".

Steps to reproduce:

Start a TCP server with following settings:

```
    ch.config().setOption(ChannelOption.SO_KEEPALIVE, true);
    ch.config().setOption(EpollChannelOption.TCP_KEEPCNT, 5);
    ch.config().setOption(EpollChannelOption.TCP_KEEPIDLE, 1);
    ch.config().setOption(EpollChannelOption.TCP_KEEPINTVL, 1);
```

Optionally schedule a fixed rate (1s) job that writes to the connection.
Start a client connection e.g. from a VM, then break the connection. Connection breakage is not detected if the job is scheduled.

Fix:

Add code to set option TCP_USER_TIMEOUT = 1000ms, observe that now connection is timeouted even if it's written to.

Tested on:

`Linux 3.13.0-39-generic #66-Ubuntu SMP Tue Oct 28 13:30:27 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux`


Issue comments:

Pull request review comments:
I think this a copy and paste error. 

TCP_USER_TIMEOUT.

Remoce extra empty line

Remove extra empty line


Patch that resolved the issue:
diff --git a/transport-native-epoll/src/main/c/io_netty_channel_epoll_Native.c b/transport-native-epoll/src/main/c/io_netty_channel_epoll_Native.c
index cc1915fefa8..5fcc2e77ae9 100644
--- a/transport-native-epoll/src/main/c/io_netty_channel_epoll_Native.c
+++ b/transport-native-epoll/src/main/c/io_netty_channel_epoll_Native.c
@@ -1271,6 +1271,10 @@ JNIEXPORT void Java_io_netty_channel_epoll_Native_setTcpKeepCnt(JNIEnv* env, jcl
     setOption(env, fd, IPPROTO_TCP, TCP_KEEPCNT, &optval, sizeof(optval));
 }
 
+JNIEXPORT void Java_io_netty_channel_epoll_Native_setTcpUserTimeout(JNIEnv* env, jclass clazz, jint fd, jint optval) {
+    setOption(env, fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &optval, sizeof(optval));
+}
+
 JNIEXPORT void JNICALL Java_io_netty_channel_epoll_Native_setIpFreeBind(JNIEnv* env, jclass clazz, jint fd, jint optval) {
     setOption(env, fd, IPPROTO_IP, IP_FREEBIND, &optval, sizeof(optval));
 }
@@ -1391,6 +1395,14 @@ JNIEXPORT jint JNICALL Java_io_netty_channel_epoll_Native_getTcpKeepCnt(JNIEnv*
      return optval;
 }
 
+JNIEXPORT jint JNICALL Java_io_netty_channel_epoll_Native_getTcpUserTimeout(JNIEnv* env, jclass clazz, jint fd) {
+     int optval;
+     if (getOption(env, fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &optval, sizeof(optval)) == -1) {
+         return -1;
+     }
+     return optval;
+}
+
 JNIEXPORT jint JNICALL Java_io_netty_channel_epoll_Native_isIpFreeBind(JNIEnv* env, jclass clazz, jint fd) {
      int optval;
      if (getOption(env, fd, IPPROTO_TCP, IP_FREEBIND, &optval, sizeof(optval)) == -1) {
diff --git a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollChannelOption.java b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollChannelOption.java
index 4a3c5d19b55..1f231f2f80c 100644
--- a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollChannelOption.java
+++ b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollChannelOption.java
@@ -26,6 +26,7 @@ public final class EpollChannelOption<T> extends ChannelOption<T> {
     public static final ChannelOption<Integer> TCP_KEEPIDLE = valueOf("TCP_KEEPIDLE");
     public static final ChannelOption<Integer> TCP_KEEPINTVL = valueOf("TCP_KEEPINTVL");
     public static final ChannelOption<Integer> TCP_KEEPCNT = valueOf("TCP_KEEPCNT");
+    public static final ChannelOption<Integer> TCP_USER_TIMEOUT = valueOf("TCP_USER_TIMEOUT");
     public static final ChannelOption<Boolean> IP_FREEBIND = valueOf("IP_FREEBIND");
 
     public static final ChannelOption<DomainSocketReadMode> DOMAIN_SOCKET_READ_MODE =
diff --git a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollSocketChannelConfig.java b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollSocketChannelConfig.java
index 2f89403a1be..65337c3e23e 100644
--- a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollSocketChannelConfig.java
+++ b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollSocketChannelConfig.java
@@ -94,6 +94,9 @@ public <T> T getOption(ChannelOption<T> option) {
         if (option == EpollChannelOption.TCP_KEEPCNT) {
             return (T) Integer.valueOf(getTcpKeepCnt());
         }
+        if (option == EpollChannelOption.TCP_USER_TIMEOUT) {
+            return (T) Integer.valueOf(getTcpUserTimeout());
+        }
         return super.getOption(option);
     }
 
@@ -127,6 +130,8 @@ public <T> boolean setOption(ChannelOption<T> option, T value) {
             setTcpKeepCntl((Integer) value);
         } else if (option == EpollChannelOption.TCP_KEEPINTVL) {
             setTcpKeepIntvl((Integer) value);
+        } else if (option == EpollChannelOption.TCP_USER_TIMEOUT) {
+            setTcpUserTimeout((Integer) value);
         } else {
             return super.setOption(option, value);
         }
@@ -205,6 +210,13 @@ public int getTcpKeepCnt() {
         return Native.getTcpKeepCnt(channel.fd().intValue());
     }
 
+    /**
+     * Get the {@code TCP_USER_TIMEOUT} option on the socket. See {@code man 7 tcp} for more details.
+     */
+    public int getTcpUserTimeout() {
+        return Native.getTcpUserTimeout(channel.fd().intValue());
+    }
+
     @Override
     public EpollSocketChannelConfig setKeepAlive(boolean keepAlive) {
         Native.setKeepAlive(channel.fd().intValue(), keepAlive ? 1 : 0);
@@ -297,6 +309,14 @@ public EpollSocketChannelConfig setTcpKeepCntl(int probes) {
         return this;
     }
 
+    /**
+     * Set the {@code TCP_USER_TIMEOUT} option on the socket. See {@code man 7 tcp} for more details.
+     */
+    public EpollSocketChannelConfig setTcpUserTimeout(int milliseconds) {
+        Native.setTcpUserTimeout(channel.fd().intValue(), milliseconds);
+        return this;
+    }
+
     @Override
     public boolean isAllowHalfClosure() {
         return allowHalfClosure;
diff --git a/transport-native-epoll/src/main/java/io/netty/channel/epoll/Native.java b/transport-native-epoll/src/main/java/io/netty/channel/epoll/Native.java
index 4def6e42b0e..3a7136809d2 100644
--- a/transport-native-epoll/src/main/java/io/netty/channel/epoll/Native.java
+++ b/transport-native-epoll/src/main/java/io/netty/channel/epoll/Native.java
@@ -635,6 +635,7 @@ public static void shutdown(int fd, boolean read, boolean write) throws IOExcept
     public static native int getTcpKeepIdle(int fd);
     public static native int getTcpKeepIntvl(int fd);
     public static native int getTcpKeepCnt(int fd);
+    public static native int getTcpUserTimeout(int milliseconds);
     public static native int getSoError(int fd);
     public static native int isIpFreeBind(int fd);
 
@@ -652,6 +653,7 @@ public static void shutdown(int fd, boolean read, boolean write) throws IOExcept
     public static native void setTcpKeepIdle(int fd, int seconds);
     public static native void setTcpKeepIntvl(int fd, int seconds);
     public static native void setTcpKeepCnt(int fd, int probes);
+    public static native void setTcpUserTimeout(int fd, int milliseconds);
     public static native void setIpFreeBind(int fd, int freeBind);
     public static void tcpInfo(int fd, EpollTcpInfo info) {
         tcpInfo0(fd, info.info);


Test patch: