From 3cc405296310643bccddc8c81998c97f25b3201c Mon Sep 17 00:00:00 2001 From: Scott Mitchell Date: Thu, 19 Jan 2017 08:31:34 -0800 Subject: [PATCH] New native transport for kqueue Motivation: We currently don't have a native transport which supports kqueue https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2. This can be useful for BSD systems such as MacOS to take advantage of native features, and provide feature parity with the Linux native transport. Modifications: - Make a new transport-native-unix-common module with all the java classes and JNI code for generic unix items. This module will build a static library for each unix platform, and included in the dynamic libraries used for JNI (e.g. transport-native-epoll, and eventually kqueue). - Make a new transport-native-unix-common-tests module where the tests for the transport-native-unix-common module will live. This is so each unix platform can inherit from these test and ensure they pass. - Add a new transport-native-kqueue module which uses JNI to directly interact with kqueue Result: JNI support for kqueue. Fixes https://github.com/netty/netty/issues/2448 Fixes https://github.com/netty/netty/issues/4231 --- all/pom.xml | 43 +- .../src/main/java/io/netty/util/NetUtil.java | 56 +- .../io/netty/util/internal/EmptyArrays.java | 1 + .../util/internal/PlatformDependent.java | 4 + .../util/internal/PlatformDependent0.java | 4 + microbench/pom.xml | 2 +- pom.xml | 125 +-- tarball/pom.xml | 5 + .../SocketShutdownOutputBySelfTest.java | 13 +- transport-native-epoll/pom.xml | 123 ++- .../src/main/c/netty_epoll_linuxsocket.c | 346 ++++++++ .../src/main/c/netty_epoll_linuxsocket.h | 26 + .../src/main/c/netty_epoll_native.c | 467 +--------- .../src/main/c/netty_unix_util.c | 55 -- .../channel/epoll/AbstractEpollChannel.java | 40 +- .../epoll/AbstractEpollServerChannel.java | 31 +- .../epoll/AbstractEpollStreamChannel.java | 127 +-- .../channel/epoll/EpollChannelConfig.java | 1 - .../channel/epoll/EpollChannelOption.java | 9 +- .../channel/epoll/EpollDatagramChannel.java | 44 +- .../epoll/EpollDatagramChannelConfig.java | 25 +- .../epoll/EpollDomainSocketChannel.java | 42 +- .../netty/channel/epoll/EpollEventLoop.java | 13 +- .../channel/epoll/EpollEventLoopGroup.java | 4 + .../epoll/EpollRecvByteAllocatorHandle.java | 4 +- .../epoll/EpollServerChannelConfig.java | 16 +- .../epoll/EpollServerDomainSocketChannel.java | 21 +- .../epoll/EpollServerSocketChannel.java | 32 +- .../epoll/EpollServerSocketChannelConfig.java | 12 +- .../channel/epoll/EpollSocketChannel.java | 33 +- .../epoll/EpollSocketChannelConfig.java | 69 +- .../io/netty/channel/epoll/LinuxSocket.java | 162 ++++ .../java/io/netty/channel/epoll/Native.java | 82 +- .../epoll/NativeDatagramPacketArray.java | 8 +- .../io/netty/channel/epoll/TcpMd5Util.java | 4 +- .../EpollAbstractDomainSocketEchoTest.java | 2 +- .../epoll/EpollSocketChannelConfigTest.java | 23 +- .../netty/channel/epoll/EpollSocketTest.java | 63 ++ .../epoll/EpollSocketTestPermutation.java | 9 +- transport-native-kqueue/pom.xml | 197 +++++ .../src/main/c/netty_kqueue_bsdsocket.c | 299 +++++++ .../src/main/c/netty_kqueue_bsdsocket.h | 25 + .../src/main/c/netty_kqueue_eventarray.c | 124 +++ .../src/main/c/netty_kqueue_eventarray.h | 25 + .../src/main/c/netty_kqueue_native.c | 307 +++++++ .../channel/kqueue/AbstractKQueueChannel.java | 470 ++++++++++ .../kqueue/AbstractKQueueServerChannel.java | 127 +++ .../kqueue/AbstractKQueueStreamChannel.java | 808 ++++++++++++++++++ .../io/netty/channel/kqueue/AcceptFilter.java | 61 ++ .../io/netty/channel/kqueue/BsdSocket.java | 119 +++ .../java/io/netty/channel/kqueue/KQueue.java | 86 ++ .../channel/kqueue/KQueueChannelConfig.java | 156 ++++ .../channel/kqueue/KQueueChannelOption.java | 39 + .../channel/kqueue/KQueueDatagramChannel.java | 552 ++++++++++++ .../kqueue/KQueueDatagramChannelConfig.java | 387 +++++++++ .../kqueue/KQueueDomainSocketChannel.java | 183 ++++ .../KQueueDomainSocketChannelConfig.java | 154 ++++ .../channel/kqueue/KQueueEventArray.java | 144 ++++ .../netty/channel/kqueue/KQueueEventLoop.java | 370 ++++++++ .../channel/kqueue/KQueueEventLoopGroup.java | 134 +++ .../kqueue/KQueueRecvByteAllocatorHandle.java | 127 +++ .../kqueue/KQueueServerChannelConfig.java | 201 +++++ .../KQueueServerDomainSocketChannel.java | 96 +++ .../kqueue/KQueueServerSocketChannel.java | 87 ++ .../KQueueServerSocketChannelConfig.java | 201 +++++ .../channel/kqueue/KQueueSocketChannel.java | 176 ++++ .../kqueue/KQueueSocketChannelConfig.java | 386 +++++++++ .../KQueueStaticallyReferencedJniMethods.java | 43 + .../java/io/netty/channel/kqueue/Native.java | 110 +++ .../netty/channel/kqueue/NativeLongArray.java | 87 ++ .../io/netty/channel/kqueue/package-info.java | 23 + .../KQueueAbstractDomainSocketEchoTest.java | 27 + .../kqueue/KQueueChannelConfigTest.java | 55 ++ .../kqueue/KQueueDatagramUnicastTest.java | 29 + .../kqueue/KQueueDomainSocketEchoTest.java | 35 + .../kqueue/KQueueDomainSocketFdTest.java | 104 +++ .../KQueueDomainSocketFileRegionTest.java | 35 + ...KQueueDomainSocketFixedLengthEchoTest.java | 37 + .../KQueueDomainSocketGatheringWriteTest.java | 37 + .../KQueueDomainSocketObjectEchoTest.java | 36 + .../kqueue/KQueueDomainSocketSslEchoTest.java | 47 + .../KQueueDomainSocketSslGreetingTest.java | 42 + .../KQueueDomainSocketStartTlsTest.java | 42 + .../KQueueDomainSocketStringEchoTest.java | 36 + .../kqueue/KQueueETSocketAutoReadTest.java | 31 + .../KQueueETSocketExceptionHandlingTest.java | 31 + .../kqueue/KQueueETSocketHalfClosedTest.java | 30 + .../kqueue/KQueueETSocketReadPendingTest.java | 31 + ...RcvAllocatorOverrideSocketSslEchoTest.java | 41 + .../KQueueServerSocketChannelConfigTest.java | 82 ++ .../kqueue/KQueueSocketChannelConfigTest.java | 125 +++ ...QueueSocketChannelNotYetConnectedTest.java | 29 + .../kqueue/KQueueSocketConnectTest.java | 31 + .../KQueueSocketConnectionAttemptTest.java | 33 + .../channel/kqueue/KQueueSocketEchoTest.java | 31 + .../kqueue/KQueueSocketFileRegionTest.java | 31 + .../KQueueSocketFixedLengthEchoTest.java | 31 + .../KQueueSocketGatheringWriteTest.java | 31 + .../KQueueSocketMultipleConnectTest.java | 42 + .../kqueue/KQueueSocketObjectEchoTest.java | 31 + .../channel/kqueue/KQueueSocketRstTest.java | 49 ++ .../KQueueSocketShutdownOutputByPeerTest.java | 29 + .../KQueueSocketShutdownOutputBySelfTest.java | 29 + .../kqueue/KQueueSocketSslEchoTest.java | 41 + .../kqueue/KQueueSocketSslGreetingTest.java | 36 + .../kqueue/KQueueSocketStartTlsTest.java | 36 + .../kqueue/KQueueSocketStringEchoTest.java | 31 + .../channel/kqueue/KQueueSocketTest.java | 62 ++ .../kqueue/KQueueSocketTestPermutation.java | 172 ++++ transport-native-unix-common-tests/pom.xml | 49 ++ .../netty/channel/unix/tests}/SocketTest.java | 53 +- .../channel/unix/tests/UnixTestUtils.java | 41 + .../channel/unix/tests/package-info.java | 20 + transport-native-unix-common/Makefile | 50 ++ transport-native-unix-common/pom.xml | 168 ++++ .../src/main/c/netty_unix_errors.c | 0 .../src/main/c/netty_unix_errors.h | 0 .../src/main/c/netty_unix_filedescriptor.c | 2 +- .../src/main/c/netty_unix_filedescriptor.h | 0 .../src/main/c/netty_unix_limits.c | 81 ++ .../src/main/c/netty_unix_limits.h | 25 + .../src/main/c/netty_unix_socket.c | 320 ++++--- .../src/main/c/netty_unix_socket.h | 2 +- .../src/main/c/netty_unix_util.c | 165 ++++ .../src/main/c/netty_unix_util.h | 36 + .../channel/unix/DatagramSocketAddress.java | 0 .../channel/unix/DomainSocketAddress.java | 0 .../channel/unix/DomainSocketChannel.java | 0 .../unix/DomainSocketChannelConfig.java | 0 .../channel/unix/DomainSocketReadMode.java | 0 .../java/io/netty/channel/unix/Errors.java | 0 .../ErrorsStaticallyReferencedJniMethods.java | 0 .../io/netty/channel/unix/FileDescriptor.java | 6 +- .../java/io/netty/channel/unix}/IovArray.java | 36 +- .../java/io/netty/channel/unix/Limits.java | 31 + .../LimitsStaticallyReferencedJniMethods.java | 37 + .../netty/channel/unix/NativeInetAddress.java | 0 .../netty/channel/unix/PeerCredentials.java | 36 +- .../unix/ServerDomainSocketChannel.java | 0 .../java/io/netty/channel/unix/Socket.java | 185 ++-- .../unix/SocketWritableByteChannel.java | 79 ++ .../io/netty/channel/unix/UnixChannel.java | 0 .../netty/channel/unix/UnixChannelOption.java | 29 + .../io/netty/channel/unix/package-info.java | 0 .../io/netty/bootstrap/ServerBootstrap.java | 2 +- 145 files changed, 10346 insertions(+), 1265 deletions(-) create mode 100644 transport-native-epoll/src/main/c/netty_epoll_linuxsocket.c create mode 100644 transport-native-epoll/src/main/c/netty_epoll_linuxsocket.h delete mode 100644 transport-native-epoll/src/main/c/netty_unix_util.c create mode 100644 transport-native-epoll/src/main/java/io/netty/channel/epoll/LinuxSocket.java create mode 100644 transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSocketTest.java create mode 100644 transport-native-kqueue/pom.xml create mode 100644 transport-native-kqueue/src/main/c/netty_kqueue_bsdsocket.c create mode 100644 transport-native-kqueue/src/main/c/netty_kqueue_bsdsocket.h create mode 100644 transport-native-kqueue/src/main/c/netty_kqueue_eventarray.c create mode 100644 transport-native-kqueue/src/main/c/netty_kqueue_eventarray.h create mode 100644 transport-native-kqueue/src/main/c/netty_kqueue_native.c create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/AbstractKQueueChannel.java create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/AbstractKQueueServerChannel.java create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/AbstractKQueueStreamChannel.java create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/AcceptFilter.java create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/BsdSocket.java create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueue.java create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueChannelConfig.java create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueChannelOption.java create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueDatagramChannel.java create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueDatagramChannelConfig.java create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueDomainSocketChannel.java create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueDomainSocketChannelConfig.java create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueEventArray.java create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueEventLoop.java create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueEventLoopGroup.java create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueRecvByteAllocatorHandle.java create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueServerChannelConfig.java create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueServerDomainSocketChannel.java create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueServerSocketChannel.java create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueServerSocketChannelConfig.java create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueSocketChannel.java create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueSocketChannelConfig.java create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueStaticallyReferencedJniMethods.java create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/Native.java create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/NativeLongArray.java create mode 100644 transport-native-kqueue/src/main/java/io/netty/channel/kqueue/package-info.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueAbstractDomainSocketEchoTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueChannelConfigTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDatagramUnicastTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketEchoTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketFdTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketFileRegionTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketFixedLengthEchoTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketGatheringWriteTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketObjectEchoTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketSslEchoTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketSslGreetingTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketStartTlsTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketStringEchoTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueETSocketAutoReadTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueETSocketExceptionHandlingTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueETSocketHalfClosedTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueETSocketReadPendingTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueRcvAllocatorOverrideSocketSslEchoTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueServerSocketChannelConfigTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketChannelConfigTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketChannelNotYetConnectedTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketConnectTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketConnectionAttemptTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketEchoTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketFileRegionTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketFixedLengthEchoTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketGatheringWriteTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketMultipleConnectTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketObjectEchoTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketRstTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketShutdownOutputByPeerTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketShutdownOutputBySelfTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketSslEchoTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketSslGreetingTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketStartTlsTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketStringEchoTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketTest.java create mode 100644 transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketTestPermutation.java create mode 100644 transport-native-unix-common-tests/pom.xml rename {transport-native-epoll/src/test/java/io/netty/channel/unix => transport-native-unix-common-tests/src/main/java/io/netty/channel/unix/tests}/SocketTest.java (64%) create mode 100644 transport-native-unix-common-tests/src/main/java/io/netty/channel/unix/tests/UnixTestUtils.java create mode 100644 transport-native-unix-common-tests/src/main/java/io/netty/channel/unix/tests/package-info.java create mode 100644 transport-native-unix-common/Makefile create mode 100644 transport-native-unix-common/pom.xml rename {transport-native-epoll => transport-native-unix-common}/src/main/c/netty_unix_errors.c (100%) rename {transport-native-epoll => transport-native-unix-common}/src/main/c/netty_unix_errors.h (100%) rename {transport-native-epoll => transport-native-unix-common}/src/main/c/netty_unix_filedescriptor.c (99%) rename {transport-native-epoll => transport-native-unix-common}/src/main/c/netty_unix_filedescriptor.h (100%) create mode 100644 transport-native-unix-common/src/main/c/netty_unix_limits.c create mode 100644 transport-native-unix-common/src/main/c/netty_unix_limits.h rename {transport-native-epoll => transport-native-unix-common}/src/main/c/netty_unix_socket.c (77%) rename {transport-native-epoll => transport-native-unix-common}/src/main/c/netty_unix_socket.h (98%) create mode 100644 transport-native-unix-common/src/main/c/netty_unix_util.c rename {transport-native-epoll => transport-native-unix-common}/src/main/c/netty_unix_util.h (57%) rename {transport-native-epoll => transport-native-unix-common}/src/main/java/io/netty/channel/unix/DatagramSocketAddress.java (100%) rename {transport-native-epoll => transport-native-unix-common}/src/main/java/io/netty/channel/unix/DomainSocketAddress.java (100%) rename {transport-native-epoll => transport-native-unix-common}/src/main/java/io/netty/channel/unix/DomainSocketChannel.java (100%) rename {transport-native-epoll => transport-native-unix-common}/src/main/java/io/netty/channel/unix/DomainSocketChannelConfig.java (100%) rename {transport-native-epoll => transport-native-unix-common}/src/main/java/io/netty/channel/unix/DomainSocketReadMode.java (100%) rename {transport-native-epoll => transport-native-unix-common}/src/main/java/io/netty/channel/unix/Errors.java (100%) rename {transport-native-epoll => transport-native-unix-common}/src/main/java/io/netty/channel/unix/ErrorsStaticallyReferencedJniMethods.java (100%) rename {transport-native-epoll => transport-native-unix-common}/src/main/java/io/netty/channel/unix/FileDescriptor.java (97%) rename {transport-native-epoll/src/main/java/io/netty/channel/epoll => transport-native-unix-common/src/main/java/io/netty/channel/unix}/IovArray.java (89%) create mode 100644 transport-native-unix-common/src/main/java/io/netty/channel/unix/Limits.java create mode 100644 transport-native-unix-common/src/main/java/io/netty/channel/unix/LimitsStaticallyReferencedJniMethods.java rename {transport-native-epoll => transport-native-unix-common}/src/main/java/io/netty/channel/unix/NativeInetAddress.java (100%) rename {transport-native-epoll => transport-native-unix-common}/src/main/java/io/netty/channel/unix/PeerCredentials.java (59%) rename {transport-native-epoll => transport-native-unix-common}/src/main/java/io/netty/channel/unix/ServerDomainSocketChannel.java (100%) rename {transport-native-epoll => transport-native-unix-common}/src/main/java/io/netty/channel/unix/Socket.java (74%) create mode 100644 transport-native-unix-common/src/main/java/io/netty/channel/unix/SocketWritableByteChannel.java rename {transport-native-epoll => transport-native-unix-common}/src/main/java/io/netty/channel/unix/UnixChannel.java (100%) create mode 100644 transport-native-unix-common/src/main/java/io/netty/channel/unix/UnixChannelOption.java rename {transport-native-epoll => transport-native-unix-common}/src/main/java/io/netty/channel/unix/package-info.java (100%) diff --git a/all/pom.xml b/all/pom.xml index fde32a0fe7..c59748a019 100644 --- a/all/pom.xml +++ b/all/pom.xml @@ -36,7 +36,11 @@ full - + + + uber + + @@ -162,25 +166,6 @@ - - linux - - - linux - - - - - - ${project.groupId} - netty-transport-native-epoll - ${project.version} - ${epoll.classifier} - compile - true - - - @@ -347,6 +332,22 @@ compile true + + ${project.groupId} + netty-transport-native-epoll + ${project.version} + linux-x86_64 + compile + true + + + ${project.groupId} + netty-transport-native-kqueue + ${project.version} + osx-x86_64 + compile + true + @@ -429,7 +430,7 @@ unpack-dependencies - io/netty/example/**,META-INF/native/libnetty-tcnative* + io/netty/example/**,META-INF/native/libnetty-tcnative*,META-INF/native/include/**,META-INF/native/**/*.a io/netty/**,META-INF/native/** runtime ${project.groupId} diff --git a/common/src/main/java/io/netty/util/NetUtil.java b/common/src/main/java/io/netty/util/NetUtil.java index 6944a78cfb..df53e22c73 100644 --- a/common/src/main/java/io/netty/util/NetUtil.java +++ b/common/src/main/java/io/netty/util/NetUtil.java @@ -24,6 +24,9 @@ import io.netty.util.internal.logging.InternalLoggerFactory; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; @@ -271,12 +274,27 @@ public final class NetUtil { logger.debug("{}: {}", file, somaxconn); } } else { - if (logger.isDebugEnabled()) { - logger.debug("{}: {} (non-existent)", file, somaxconn); + // Try to get from sysctl + Integer tmp = null; + if (SystemPropertyUtil.getBoolean("io.netty.net.somaxconn.trySysctl", false)) { + tmp = sysctlGetInt("kern.ipc.somaxconn"); + if (tmp == null) { + tmp = sysctlGetInt("kern.ipc.soacceptqueue"); + if (tmp != null) { + somaxconn = tmp; + } + } else { + somaxconn = tmp; + } + } + + if (tmp == null) { + logger.debug("Failed to get SOMAXCONN from sysctl and file {}. Default: {}", file, + somaxconn); } } } catch (Exception e) { - logger.debug("Failed to get SOMAXCONN from: {}", file, e); + logger.debug("Failed to get SOMAXCONN from sysctl and file {}. Default: {}", file, somaxconn, e); } finally { if (in != null) { try { @@ -291,6 +309,38 @@ public final class NetUtil { }); } + /** + * This will execute sysctl with the {@code sysctlKey} + * which is expected to return the numeric value for for {@code sysctlKey}. + * @param sysctlKey The key which the return value corresponds to. + * @return The sysctl value for {@code sysctlKey}. + */ + private static Integer sysctlGetInt(String sysctlKey) throws IOException { + Process process = new ProcessBuilder("sysctl", sysctlKey).start(); + try { + InputStream is = process.getInputStream(); + InputStreamReader isr = new InputStreamReader(is); + BufferedReader br = new BufferedReader(isr); + try { + String line = br.readLine(); + if (line.startsWith(sysctlKey)) { + for (int i = line.length() - 1; i > sysctlKey.length(); --i) { + if (!Character.isDigit(line.charAt(i))) { + return Integer.valueOf(line.substring(i + 1, line.length())); + } + } + } + return null; + } finally { + br.close(); + } + } finally { + if (process != null) { + process.destroy(); + } + } + } + /** * Returns {@code true} if IPv4 should be used even if the system supports both IPv4 and IPv6. Setting this * property to {@code true} will disable IPv6 support. The default value of this property is {@code false}. diff --git a/common/src/main/java/io/netty/util/internal/EmptyArrays.java b/common/src/main/java/io/netty/util/internal/EmptyArrays.java index e448ca5e26..426027e297 100644 --- a/common/src/main/java/io/netty/util/internal/EmptyArrays.java +++ b/common/src/main/java/io/netty/util/internal/EmptyArrays.java @@ -24,6 +24,7 @@ import java.security.cert.X509Certificate; public final class EmptyArrays { + public static final int[] EMPTY_INTS = {}; public static final byte[] EMPTY_BYTES = {}; public static final char[] EMPTY_CHARS = {}; public static final Object[] EMPTY_OBJECTS = {}; diff --git a/common/src/main/java/io/netty/util/internal/PlatformDependent.java b/common/src/main/java/io/netty/util/internal/PlatformDependent.java index ef07d8fbad..1ec6536990 100644 --- a/common/src/main/java/io/netty/util/internal/PlatformDependent.java +++ b/common/src/main/java/io/netty/util/internal/PlatformDependent.java @@ -287,6 +287,10 @@ public final class PlatformDependent { PlatformDependent0.freeMemory(address); } + public static long reallocateMemory(long address, long newSize) { + return PlatformDependent0.reallocateMemory(address, newSize); + } + /** * Raises an exception bypassing compiler checks for checked exceptions. */ diff --git a/common/src/main/java/io/netty/util/internal/PlatformDependent0.java b/common/src/main/java/io/netty/util/internal/PlatformDependent0.java index b755e7392b..21178035b4 100644 --- a/common/src/main/java/io/netty/util/internal/PlatformDependent0.java +++ b/common/src/main/java/io/netty/util/internal/PlatformDependent0.java @@ -752,6 +752,10 @@ final class PlatformDependent0 { UNSAFE.freeMemory(address); } + static long reallocateMemory(long address, long newSize) { + return UNSAFE.reallocateMemory(address, newSize); + } + private PlatformDependent0() { } } diff --git a/microbench/pom.xml b/microbench/pom.xml index e49a782c7b..c30b38c21b 100644 --- a/microbench/pom.xml +++ b/microbench/pom.xml @@ -51,7 +51,7 @@ ${project.groupId} netty-transport-native-epoll ${project.version} - ${epoll.classifier} + ${jni.classifier} diff --git a/pom.xml b/pom.xml index ec6855180a..415b41809a 100644 --- a/pom.xml +++ b/pom.xml @@ -146,55 +146,72 @@ + transport-native-unix-common-tests + transport-native-unix-common transport-native-epoll - - restricted-release - - - - maven-enforcer-plugin - - - enforce-release-environment - - enforce - - - - - - Release process must be performed on linux-x86_64. - - os.detected.classifier - ^linux-x86_64$ - - - - Release process must be performed on RHEL 6.8 or its derivatives. - - - /etc/redhat-release - - release 6.8 - - - - - - - - + mac + + + mac + + + + transport-native-unix-common-tests + transport-native-unix-common + transport-native-kqueue + + + + freebsd + + + unix + freebsd + + + + transport-native-unix-common-tests + transport-native-unix-common + transport-native-kqueue + + + + openbsd + + + unix + openbsd + + + + transport-native-unix-common-tests + transport-native-unix-common + transport-native-kqueue + + + + + uber + + + uber + + + + all + tarball + - - com.ceilfors.maven.plugin - enforcer-rules - 1.2.0 - - - maven-surefire-plugin diff --git a/tarball/pom.xml b/tarball/pom.xml index d7fe4ff047..7fec0ad069 100644 --- a/tarball/pom.xml +++ b/tarball/pom.xml @@ -96,6 +96,11 @@ full + + + uber + + diff --git a/testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketShutdownOutputBySelfTest.java b/testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketShutdownOutputBySelfTest.java index 150ba0f09c..2ccc1b3fb0 100644 --- a/testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketShutdownOutputBySelfTest.java +++ b/testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketShutdownOutputBySelfTest.java @@ -30,7 +30,10 @@ import java.nio.channels.ClosedChannelException; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class SocketShutdownOutputBySelfTest extends AbstractClientSocketTest { @@ -43,9 +46,10 @@ public class SocketShutdownOutputBySelfTest extends AbstractClientSocketTest { TestHandler h = new TestHandler(); ServerSocket ss = new ServerSocket(); Socket s = null; + SocketChannel ch = null; try { ss.bind(addr); - SocketChannel ch = (SocketChannel) cb.handler(h).connect().sync().channel(); + ch = (SocketChannel) cb.handler(h).connect().sync().channel(); assertTrue(ch.isActive()); assertFalse(ch.isOutputShutdown()); @@ -68,12 +72,15 @@ public class SocketShutdownOutputBySelfTest extends AbstractClientSocketTest { assertTrue(h.ch.isOutputShutdown()); // If half-closed, the peer should be able to write something. - s.getOutputStream().write(1); + s.getOutputStream().write(new byte[] { 1 }); assertEquals(1, (int) h.queue.take()); } finally { if (s != null) { s.close(); } + if (ch != null) { + ch.close(); + } ss.close(); } } diff --git a/transport-native-epoll/pom.xml b/transport-native-epoll/pom.xml index 56234ae034..dc2800c097 100644 --- a/transport-native-epoll/pom.xml +++ b/transport-native-epoll/pom.xml @@ -27,11 +27,81 @@ jar - LDFLAGS=-Wl,--no-as-needed -lrt --add-exports java.base/sun.security.x509=ALL-UNNAMED --add-opens=java.base/java.nio=ALL-UNNAMED + netty-unix-common + ${project.build.directory}/unix-common-lib + ${unix.common.lib.dir}/META-INF/native/lib + ${unix.common.lib.dir}/META-INF/native/include + LDFLAGS=-L${unix.common.lib.unpacked.dir} -Wl,--no-as-needed -lrt -Wl,--whole-archive -l${unix.common.lib.name} -Wl,--no-whole-archive + + + + restricted-release-epoll + + + + + maven-enforcer-plugin + 1.4.1 + + + + com.ceilfors.maven.plugin + enforcer-rules + 1.2.0 + + + + + + + + maven-enforcer-plugin + + + enforce-release-environment + + enforce + + + + + + Release process must be performed on linux-x86_64. + + os.detected.classifier + ^linux-x86_64$ + + + + Release process must be performed on RHEL 6.8 or its derivatives. + + + /etc/redhat-release + + release 6.8 + + + + + + + + + + + io.netty @@ -43,6 +113,22 @@ netty-buffer ${project.version} + + io.netty + netty-transport-native-unix-common + ${project.version} + ${jni.classifier} + + true + + + io.netty + netty-transport-native-unix-common + ${project.version} + io.netty netty-transport @@ -54,6 +140,12 @@ ${project.version} test + + io.netty + netty-transport-native-unix-common-tests + ${project.version} + test + ${project.groupId} ${tcnative.artifactId} @@ -64,6 +156,29 @@ + + maven-dependency-plugin + + + + unpack + generate-sources + + unpack-dependencies + + + ${project.groupId} + netty-transport-native-unix-common + ${jni.classifier} + ${unix.common.lib.dir} + META-INF/native/** + false + true + + + + + org.fusesource.hawtjni maven-hawtjni-plugin @@ -122,7 +237,7 @@ true ${project.build.outputDirectory}/META-INF/MANIFEST.MF - ${epoll.classifier} + ${jni.classifier} @@ -201,7 +316,7 @@ ${linux.sendmmsg.support}${glibc.sendmmsg.support} .*IO_NETTY_SENDMSSG_NOT_FOUND.* - CFLAGS=-O3 -DIO_NETTY_SENDMMSG_NOT_FOUND -Werror -fno-omit-frame-pointer -Wunused-variable + CFLAGS=-O3 -DIO_NETTY_SENDMMSG_NOT_FOUND -Werror -fno-omit-frame-pointer -Wunused-variable -I${unix.common.include.unpacked.dir} false @@ -217,7 +332,7 @@ ${jni.compiler.args.cflags} ^((?!CFLAGS=).)*$ - CFLAGS=-O3 -Werror -fno-omit-frame-pointer -Wunused-variable + CFLAGS=-O3 -Werror -fno-omit-frame-pointer -Wunused-variable -I${unix.common.include.unpacked.dir} false diff --git a/transport-native-epoll/src/main/c/netty_epoll_linuxsocket.c b/transport-native-epoll/src/main/c/netty_epoll_linuxsocket.c new file mode 100644 index 0000000000..2e11b51a7b --- /dev/null +++ b/transport-native-epoll/src/main/c/netty_epoll_linuxsocket.c @@ -0,0 +1,346 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +/* + * Since glibc 2.8, the _GNU_SOURCE feature test macro must be defined + * (before including any header files) in order to obtain the + * definition of the ucred structure. See + */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include // TCP_NOTSENT_LOWAT is a linux specific define +#include "netty_unix_filedescriptor.h" +#include "netty_unix_socket.h" +#include "netty_unix_errors.h" +#include "netty_unix_util.h" +#include "netty_epoll_linuxsocket.h" + +// TCP_FASTOPEN is defined in linux 3.7. We define this here so older kernels can compile. +#ifndef TCP_FASTOPEN +#define TCP_FASTOPEN 23 +#endif + +// TCP_NOTSENT_LOWAT is defined in linux 3.12. We define this here so older kernels can compile. +#ifndef TCP_NOTSENT_LOWAT +#define TCP_NOTSENT_LOWAT 25 +#endif + +static jclass peerCredentialsClass = NULL; +static jmethodID peerCredentialsMethodId = NULL; + +// JNI Registered Methods Begin +static void netty_epoll_linuxsocket_setTcpCork(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_CORK, &optval, sizeof(optval)); +} + +static void netty_epoll_linuxsocket_setTcpQuickAck(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_QUICKACK, &optval, sizeof(optval)); +} + +static void netty_epoll_linuxsocket_setTcpDeferAccept(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &optval, sizeof(optval)); +} + +static void netty_epoll_linuxsocket_setTcpNotSentLowAt(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_NOTSENT_LOWAT, &optval, sizeof(optval)); +} + +static void netty_epoll_linuxsocket_setTcpFastOpen(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_FASTOPEN, &optval, sizeof(optval)); +} + +static void netty_epoll_linuxsocket_setTcpKeepIdle(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_KEEPIDLE, &optval, sizeof(optval)); +} + +static void netty_epoll_linuxsocket_setTcpKeepIntvl(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_KEEPINTVL, &optval, sizeof(optval)); +} + +static void netty_epoll_linuxsocket_setTcpKeepCnt(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_KEEPCNT, &optval, sizeof(optval)); +} + +static void netty_epoll_linuxsocket_setTcpUserTimeout(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &optval, sizeof(optval)); +} + +static void netty_epoll_linuxsocket_setIpFreeBind(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, IPPROTO_IP, IP_FREEBIND, &optval, sizeof(optval)); +} + +static void netty_epoll_linuxsocket_setTcpMd5Sig(JNIEnv* env, jclass clazz, jint fd, jbyteArray address, jint scopeId, jbyteArray key) { + struct sockaddr_storage addr; + socklen_t addrSize; + if (netty_unix_socket_initSockaddr(env, address, scopeId, 0, &addr, &addrSize) == -1) { + return; + } + + struct tcp_md5sig md5sig; + memset(&md5sig, 0, sizeof(md5sig)); + md5sig.tcpm_addr.ss_family = addr.ss_family; + + struct sockaddr_in* ipaddr; + struct sockaddr_in6* ip6addr; + + switch (addr.ss_family) { + case AF_INET: + ipaddr = (struct sockaddr_in*) &addr; + memcpy(&((struct sockaddr_in *) &md5sig.tcpm_addr)->sin_addr, &ipaddr->sin_addr, sizeof(ipaddr->sin_addr)); + break; + case AF_INET6: + ip6addr = (struct sockaddr_in6*) &addr; + memcpy(&((struct sockaddr_in6 *) &md5sig.tcpm_addr)->sin6_addr, &ip6addr->sin6_addr, sizeof(ip6addr->sin6_addr)); + break; + } + + if (key != NULL) { + md5sig.tcpm_keylen = (*env)->GetArrayLength(env, key); + (*env)->GetByteArrayRegion(env, key, 0, md5sig.tcpm_keylen, (void *) &md5sig.tcpm_key); + if ((*env)->ExceptionCheck(env) == JNI_TRUE) { + return; + } + } + + if (setsockopt(fd, IPPROTO_TCP, TCP_MD5SIG, &md5sig, sizeof(md5sig)) < 0) { + netty_unix_errors_throwChannelExceptionErrorNo(env, "setsockopt() failed: ", errno); + } +} + +static jint netty_epoll_linuxsocket_getTcpKeepIdle(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_KEEPIDLE, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jint netty_epoll_linuxsocket_getTcpKeepIntvl(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_KEEPINTVL, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jint netty_epoll_linuxsocket_getTcpKeepCnt(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_KEEPCNT, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jint netty_epoll_linuxsocket_getTcpUserTimeout(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jint netty_epoll_linuxsocket_isIpFreeBind(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_IP, IP_FREEBIND, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static void netty_epoll_linuxsocket_getTcpInfo(JNIEnv* env, jclass clazz, jint fd, jintArray array) { + struct tcp_info tcp_info; + if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_INFO, &tcp_info, sizeof(tcp_info)) == -1) { + return; + } + unsigned int cArray[32]; + cArray[0] = tcp_info.tcpi_state; + cArray[1] = tcp_info.tcpi_ca_state; + cArray[2] = tcp_info.tcpi_retransmits; + cArray[3] = tcp_info.tcpi_probes; + cArray[4] = tcp_info.tcpi_backoff; + cArray[5] = tcp_info.tcpi_options; + cArray[6] = tcp_info.tcpi_snd_wscale; + cArray[7] = tcp_info.tcpi_rcv_wscale; + cArray[8] = tcp_info.tcpi_rto; + cArray[9] = tcp_info.tcpi_ato; + cArray[10] = tcp_info.tcpi_snd_mss; + cArray[11] = tcp_info.tcpi_rcv_mss; + cArray[12] = tcp_info.tcpi_unacked; + cArray[13] = tcp_info.tcpi_sacked; + cArray[14] = tcp_info.tcpi_lost; + cArray[15] = tcp_info.tcpi_retrans; + cArray[16] = tcp_info.tcpi_fackets; + cArray[17] = tcp_info.tcpi_last_data_sent; + cArray[18] = tcp_info.tcpi_last_ack_sent; + cArray[19] = tcp_info.tcpi_last_data_recv; + cArray[20] = tcp_info.tcpi_last_ack_recv; + cArray[21] = tcp_info.tcpi_pmtu; + cArray[22] = tcp_info.tcpi_rcv_ssthresh; + cArray[23] = tcp_info.tcpi_rtt; + cArray[24] = tcp_info.tcpi_rttvar; + cArray[25] = tcp_info.tcpi_snd_ssthresh; + cArray[26] = tcp_info.tcpi_snd_cwnd; + cArray[27] = tcp_info.tcpi_advmss; + cArray[28] = tcp_info.tcpi_reordering; + cArray[29] = tcp_info.tcpi_rcv_rtt; + cArray[30] = tcp_info.tcpi_rcv_space; + cArray[31] = tcp_info.tcpi_total_retrans; + + (*env)->SetIntArrayRegion(env, array, 0, 32, cArray); +} + +static jint netty_epoll_linuxsocket_isTcpCork(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_CORK, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jint netty_epoll_linuxsocket_getTcpDeferAccept(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jint netty_epoll_linuxsocket_isTcpQuickAck(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_QUICKACK, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jint netty_epoll_linuxsocket_getTcpNotSentLowAt(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_NOTSENT_LOWAT, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jobject netty_epoll_linuxsocket_getPeerCredentials(JNIEnv *env, jclass clazz, jint fd) { + struct ucred credentials; + if(netty_unix_socket_getOption(env,fd, SOL_SOCKET, SO_PEERCRED, &credentials, sizeof (credentials)) == -1) { + return NULL; + } + jintArray gids = (*env)->NewIntArray(env, 1); + (*env)->SetIntArrayRegion(env, gids, 0, 1, (jint*) &credentials.gid); + return (*env)->NewObject(env, peerCredentialsClass, peerCredentialsMethodId, credentials.pid, credentials.uid, gids); +} +// JNI Registered Methods End + +// JNI Method Registration Table Begin +static const JNINativeMethod fixed_method_table[] = { + { "setTcpCork", "(II)V", (void *) netty_epoll_linuxsocket_setTcpCork }, + { "setTcpQuickAck", "(II)V", (void *) netty_epoll_linuxsocket_setTcpQuickAck }, + { "setTcpDeferAccept", "(II)V", (void *) netty_epoll_linuxsocket_setTcpDeferAccept }, + { "setTcpNotSentLowAt", "(II)V", (void *) netty_epoll_linuxsocket_setTcpNotSentLowAt }, + { "isTcpCork", "(I)I", (void *) netty_epoll_linuxsocket_isTcpCork }, + { "getTcpDeferAccept", "(I)I", (void *) netty_epoll_linuxsocket_getTcpDeferAccept }, + { "getTcpNotSentLowAt", "(I)I", (void *) netty_epoll_linuxsocket_getTcpNotSentLowAt }, + { "isTcpQuickAck", "(I)I", (void *) netty_epoll_linuxsocket_isTcpQuickAck }, + { "setTcpFastOpen", "(II)V", (void *) netty_epoll_linuxsocket_setTcpFastOpen }, + { "setTcpKeepIdle", "(II)V", (void *) netty_epoll_linuxsocket_setTcpKeepIdle }, + { "setTcpKeepIntvl", "(II)V", (void *) netty_epoll_linuxsocket_setTcpKeepIntvl }, + { "setTcpKeepCnt", "(II)V", (void *) netty_epoll_linuxsocket_setTcpKeepCnt }, + { "setTcpUserTimeout", "(II)V", (void *) netty_epoll_linuxsocket_setTcpUserTimeout }, + { "setIpFreeBind", "(II)V", (void *) netty_epoll_linuxsocket_setIpFreeBind }, + { "getTcpKeepIdle", "(I)I", (void *) netty_epoll_linuxsocket_getTcpKeepIdle }, + { "getTcpKeepIntvl", "(I)I", (void *) netty_epoll_linuxsocket_getTcpKeepIntvl }, + { "getTcpKeepCnt", "(I)I", (void *) netty_epoll_linuxsocket_getTcpKeepCnt }, + { "getTcpUserTimeout", "(I)I", (void *) netty_epoll_linuxsocket_getTcpUserTimeout }, + { "isIpFreeBind", "(I)I", (void *) netty_epoll_linuxsocket_isIpFreeBind }, + { "getTcpInfo", "(I[I)V", (void *) netty_epoll_linuxsocket_getTcpInfo }, + { "setTcpMd5Sig", "(I[BI[B)V", (void *) netty_epoll_linuxsocket_setTcpMd5Sig } +}; + +static const jint fixed_method_table_size = sizeof(fixed_method_table) / sizeof(fixed_method_table[0]); + +static jint dynamicMethodsTableSize() { + return fixed_method_table_size + 1; +} + +static JNINativeMethod* createDynamicMethodsTable(const char* packagePrefix) { + JNINativeMethod* dynamicMethods = malloc(sizeof(JNINativeMethod) * dynamicMethodsTableSize()); + memcpy(dynamicMethods, fixed_method_table, sizeof(fixed_method_table)); + JNINativeMethod* dynamicMethod = &dynamicMethods[fixed_method_table_size]; + char* dynamicTypeName = netty_unix_util_prepend(packagePrefix, "io/netty/channel/unix/PeerCredentials;"); + dynamicMethod->name = "getPeerCredentials"; + dynamicMethod->signature = netty_unix_util_prepend("(I)L", dynamicTypeName); + dynamicMethod->fnPtr = (void *) netty_epoll_linuxsocket_getPeerCredentials; + free(dynamicTypeName); + return dynamicMethods; +} + +static void freeDynamicMethodsTable(JNINativeMethod* dynamicMethods) { + jint fullMethodTableSize = dynamicMethodsTableSize(); + jint i = fixed_method_table_size; + for (; i < fullMethodTableSize; ++i) { + free(dynamicMethods[i].signature); + } + free(dynamicMethods); +} +// JNI Method Registration Table End + +jint netty_epoll_linuxsocket_JNI_OnLoad(JNIEnv* env, const char* packagePrefix) { + JNINativeMethod* dynamicMethods = createDynamicMethodsTable(packagePrefix); + if (netty_unix_util_register_natives(env, + packagePrefix, + "io/netty/channel/epoll/LinuxSocket", + dynamicMethods, + dynamicMethodsTableSize()) != 0) { + freeDynamicMethodsTable(dynamicMethods); + return JNI_ERR; + } + freeDynamicMethodsTable(dynamicMethods); + dynamicMethods = NULL; + + char* nettyClassName = netty_unix_util_prepend(packagePrefix, "io/netty/channel/unix/PeerCredentials"); + jclass localPeerCredsClass = (*env)->FindClass(env, nettyClassName); + free(nettyClassName); + nettyClassName = NULL; + if (localPeerCredsClass == NULL) { + // pending exception... + return JNI_ERR; + } + peerCredentialsClass = (jclass) (*env)->NewGlobalRef(env, localPeerCredsClass); + if (peerCredentialsClass == NULL) { + // out-of-memory! + netty_unix_errors_throwOutOfMemoryError(env); + return JNI_ERR; + } + peerCredentialsMethodId = (*env)->GetMethodID(env, peerCredentialsClass, "", "(II[I)V"); + if (peerCredentialsMethodId == NULL) { + netty_unix_errors_throwRuntimeException(env, "failed to get method ID: PeerCredentials.(int, int, int[])"); + return JNI_ERR; + } + + return JNI_VERSION_1_6; +} + +void netty_epoll_linuxsocket_JNI_OnUnLoad(JNIEnv* env) { + if (peerCredentialsClass != NULL) { + (*env)->DeleteGlobalRef(env, peerCredentialsClass); + peerCredentialsClass = NULL; + } +} diff --git a/transport-native-epoll/src/main/c/netty_epoll_linuxsocket.h b/transport-native-epoll/src/main/c/netty_epoll_linuxsocket.h new file mode 100644 index 0000000000..bee2f99fb6 --- /dev/null +++ b/transport-native-epoll/src/main/c/netty_epoll_linuxsocket.h @@ -0,0 +1,26 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#ifndef NETTY_EPOLL_LINUXSOCKET_H_ +#define NETTY_EPOLL_LINUXSOCKET_H_ + +#include + +// JNI initialization hooks. Users of this file are responsible for calling these in the JNI_OnLoad and JNI_OnUnload methods. +jint netty_epoll_linuxsocket_JNI_OnLoad(JNIEnv* env, const char* packagePrefix); +void netty_epoll_linuxsocket_JNI_OnUnLoad(JNIEnv* env); + +#endif diff --git a/transport-native-epoll/src/main/c/netty_epoll_native.c b/transport-native-epoll/src/main/c/netty_epoll_native.c index c5a500ca4a..14cc12f4ec 100644 --- a/transport-native-epoll/src/main/c/netty_epoll_native.c +++ b/transport-native-epoll/src/main/c/netty_epoll_native.c @@ -23,8 +23,8 @@ #include #include #include -#include // TCP_NOTSENT_LOWAT is a linux specific define #include +#include #include #include #include @@ -40,28 +40,8 @@ #include "netty_unix_socket.h" #include "netty_unix_errors.h" #include "netty_unix_util.h" - -// Define SO_REUSEPORT if not found to fix build issues. -// See https://github.com/netty/netty/issues/2558 -#ifndef SO_REUSEPORT -#define SO_REUSEPORT 15 -#endif /* SO_REUSEPORT */ - -// Define IOV_MAX if not found to limit the iov size on writev calls -// See https://github.com/netty/netty/issues/2647 -#ifndef IOV_MAX -#define IOV_MAX 1024 -#endif /* IOV_MAX */ - -// Define UIO_MAXIOV if not found -#ifndef UIO_MAXIOV -#define UIO_MAXIOV 1024 -#endif /* UIO_MAXIOV */ - -// TCP_NOTSENT_LOWAT is defined in linux 3.12. We define this here so older kernels can compile. -#ifndef TCP_NOTSENT_LOWAT -#define TCP_NOTSENT_LOWAT 25 -#endif +#include "netty_unix_limits.h" +#include "netty_epoll_linuxsocket.h" // TCP_FASTOPEN is defined in linux 3.7. We define this here so older kernels can compile. #ifndef TCP_FASTOPEN @@ -104,49 +84,9 @@ jfieldID packetPortFieldId = NULL; jfieldID packetMemoryAddressFieldId = NULL; jfieldID packetCountFieldId = NULL; -clockid_t epollWaitClock = 0; // initialized in initializeEpollWaitClock +clockid_t waitClockId = 0; // initialized by netty_unix_util_initialize_wait_clock // util methods -static jboolean initializeEpollWaitClock() { - struct timespec ts; - // First try to get a monotonic clock, as we effectively measure execution time and don't want the underlying clock - // moving unexpectedly/abruptly. - #ifdef CLOCK_MONOTONIC_COARSE - epollWaitClock = CLOCK_MONOTONIC_COARSE; - if (clock_gettime(epollWaitClock, &ts) != EINVAL) { - return JNI_TRUE; - } - #endif - #ifdef CLOCK_MONOTONIC_RAW - epollWaitClock = CLOCK_MONOTONIC_RAW; - if (clock_gettime(epollWaitClock, &ts) != EINVAL) { - return JNI_TRUE; - } - #endif - #ifdef CLOCK_MONOTONIC - epollWaitClock = CLOCK_MONOTONIC; - if (clock_gettime(epollWaitClock, &ts) != EINVAL) { - return JNI_TRUE; - } - #endif - - // Fallback to realtime ... in this case we are subject to clock movements on the system. - #ifdef CLOCK_REALTIME_COARSE - epollWaitClock = CLOCK_REALTIME_COARSE; - if (clock_gettime(epollWaitClock, &ts) != EINVAL) { - return JNI_TRUE; - } - #endif - #ifdef CLOCK_REALTIME - epollWaitClock = CLOCK_REALTIME; - if (clock_gettime(epollWaitClock, &ts) != EINVAL) { - return JNI_TRUE; - } - #endif - fprintf(stderr, "FATAL: could not find a clock for clock_gettime!\n"); - return JNI_FALSE; -} - static int getSysctlValue(const char * property, int* returnValue) { int rc = -1; FILE *fd=fopen(property, "r"); @@ -238,7 +178,7 @@ static jint netty_epoll_native_epollWait0(JNIEnv* env, jclass clazz, jint efd, j timeout = MAX_EPOLL_TIMEOUT_MSEC; } - clock_gettime(epollWaitClock, &ts); + clock_gettime(waitClockId, &ts); timeBeforeWait = ts.tv_sec * 1000 + ts.tv_nsec / 1000000; for (;;) { @@ -248,7 +188,7 @@ static jint netty_epoll_native_epollWait0(JNIEnv* env, jclass clazz, jint efd, j } if ((err = errno) == EINTR) { if (timeout > 0) { - clock_gettime(epollWaitClock, &ts); + clock_gettime(waitClockId, &ts); timeNow = ts.tv_sec * 1000 + ts.tv_nsec / 1000000; timeDiff = timeNow - timeBeforeWait; timeout -= timeDiff; @@ -293,6 +233,7 @@ static jint netty_epoll_native_epollCtlDel0(JNIEnv* env, jclass clazz, jint efd, static jint netty_epoll_native_sendmmsg0(JNIEnv* env, jclass clazz, jint fd, jobjectArray packets, jint offset, jint len) { struct mmsghdr msg[len]; struct sockaddr_storage addr[len]; + socklen_t addrSize; int i; memset(msg, 0, sizeof(msg)); @@ -304,12 +245,12 @@ static jint netty_epoll_native_sendmmsg0(JNIEnv* env, jclass clazz, jint fd, job jint scopeId = (*env)->GetIntField(env, packet, packetScopeIdFieldId); jint port = (*env)->GetIntField(env, packet, packetPortFieldId); - if (netty_unix_socket_initSockaddr(env, address, scopeId, port, &addr[i]) == -1) { + if (netty_unix_socket_initSockaddr(env, address, scopeId, port, &addr[i], &addrSize) == -1) { return -1; } msg[i].msg_hdr.msg_name = &addr[i]; - msg[i].msg_hdr.msg_namelen = sizeof(addr[i]); + msg[i].msg_hdr.msg_namelen = addrSize; msg[i].msg_hdr.msg_iov = (struct iovec*) (intptr_t) (*env)->GetLongField(env, packet, packetMemoryAddressFieldId); msg[i].msg_hdr.msg_iovlen = (*env)->GetIntField(env, packet, packetCountFieldId);; @@ -327,89 +268,6 @@ static jint netty_epoll_native_sendmmsg0(JNIEnv* env, jclass clazz, jint fd, job } return (jint) res; } -static jint netty_epoll_native_recvFd0(JNIEnv* env, jclass clazz, jint fd) { - int socketFd; - struct msghdr descriptorMessage = { 0 }; - struct iovec iov[1] = { 0 }; - char control[CMSG_SPACE(sizeof(int))] = { 0 }; - char iovecData[1]; - - descriptorMessage.msg_control = control; - descriptorMessage.msg_controllen = sizeof(control); - descriptorMessage.msg_iov = iov; - descriptorMessage.msg_iovlen = 1; - iov[0].iov_base = iovecData; - iov[0].iov_len = sizeof(iovecData); - - ssize_t res; - int err; - - for (;;) { - do { - res = recvmsg(fd, &descriptorMessage, 0); - // Keep on reading if we was interrupted - } while (res == -1 && ((err = errno) == EINTR)); - - if (res == 0) { - return 0; - } - - if (res < 0) { - return -err; - } - - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&descriptorMessage); - if (!cmsg) { - return -errno; - } - - if ((cmsg->cmsg_len == CMSG_LEN(sizeof(int))) && (cmsg->cmsg_level == SOL_SOCKET) && (cmsg->cmsg_type == SCM_RIGHTS)) { - socketFd = *((int *) CMSG_DATA(cmsg)); - // set as non blocking as we want to use it with epoll - if (fcntl(socketFd, F_SETFL, O_NONBLOCK) == -1) { - err = errno; - close(socketFd); - return -err; - } - return socketFd; - } - } -} - -static jint netty_epoll_native_sendFd0(JNIEnv* env, jclass clazz, jint socketFd, jint fd) { - struct msghdr descriptorMessage = { 0 }; - struct iovec iov[1] = { 0 }; - char control[CMSG_SPACE(sizeof(int))] = { 0 }; - char iovecData[1]; - - descriptorMessage.msg_control = control; - descriptorMessage.msg_controllen = sizeof(control); - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&descriptorMessage); - - if (cmsg) { - cmsg->cmsg_len = CMSG_LEN(sizeof(int)); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - *((int *)CMSG_DATA(cmsg)) = fd; - descriptorMessage.msg_iov = iov; - descriptorMessage.msg_iovlen = 1; - iov[0].iov_base = iovecData; - iov[0].iov_len = sizeof(iovecData); - - ssize_t res; - int err; - do { - res = sendmsg(socketFd, &descriptorMessage, 0); - // keep on writing if it was interrupted - } while (res == -1 && ((err = errno) == EINTR)); - - if (res < 0) { - return -err; - } - return (jint) res; - } - return -1; -} static jlong netty_epoll_native_sendfile0(JNIEnv* env, jclass clazz, jint fd, jobject fileRegion, jlong base_off, jlong off, jlong len) { jobject fileChannel = (*env)->GetObjectField(env, fileRegion, fileChannelFieldId); @@ -443,177 +301,6 @@ static jlong netty_epoll_native_sendfile0(JNIEnv* env, jclass clazz, jint fd, jo return res; } -static void netty_epoll_native_setReuseAddress(JNIEnv* env, jclass clazz, jint fd, jint optval) { - netty_unix_socket_setOption(env, fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); -} - -static void netty_epoll_native_setReusePort(JNIEnv* env, jclass clazz, jint fd, jint optval) { - netty_unix_socket_setOption(env, fd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)); -} - -static void netty_epoll_native_setTcpFastopen(JNIEnv* env, jclass clazz, jint fd, jint optval) { - netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_FASTOPEN, &optval, sizeof(optval)); -} - -static void netty_epoll_native_setTcpNotSentLowAt(JNIEnv* env, jclass clazz, jint fd, jint optval) { - netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_NOTSENT_LOWAT, &optval, sizeof(optval)); -} - -static void netty_epoll_native_setTrafficClass(JNIEnv* env, jclass clazz, jint fd, jint optval) { - netty_unix_socket_setOption(env, fd, IPPROTO_IP, IP_TOS, &optval, sizeof(optval)); - - /* Try to set the ipv6 equivalent, but don't throw if this is an ipv4 only socket. */ - int rc = setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &optval, sizeof(optval)); - if (rc < 0 && errno != ENOPROTOOPT) { - netty_unix_errors_throwChannelExceptionErrorNo(env, "setting ipv6 dscp failed: ", errno); - } -} - -static void netty_epoll_native_setBroadcast(JNIEnv* env, jclass clazz, jint fd, jint optval) { - netty_unix_socket_setOption(env, fd, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval)); -} - -static void netty_epoll_native_setTcpKeepIdle(JNIEnv* env, jclass clazz, jint fd, jint optval) { - netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_KEEPIDLE, &optval, sizeof(optval)); -} - -static void netty_epoll_native_setTcpKeepIntvl(JNIEnv* env, jclass clazz, jint fd, jint optval) { - netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_KEEPINTVL, &optval, sizeof(optval)); -} - -static void netty_epoll_native_setTcpKeepCnt(JNIEnv* env, jclass clazz, jint fd, jint optval) { - netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_KEEPCNT, &optval, sizeof(optval)); -} - -static void netty_epoll_native_setTcpUserTimeout(JNIEnv* env, jclass clazz, jint fd, jint optval) { - netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &optval, sizeof(optval)); -} - -static void netty_epoll_native_setIpFreeBind(JNIEnv* env, jclass clazz, jint fd, jint optval) { - netty_unix_socket_setOption(env, fd, IPPROTO_IP, IP_FREEBIND, &optval, sizeof(optval)); -} - -static jint netty_epoll_native_isReuseAddress(JNIEnv* env, jclass clazz, jint fd) { - int optval; - if (netty_unix_socket_getOption(env, fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) { - return -1; - } - return optval; -} - -static jint netty_epoll_native_isReusePort(JNIEnv* env, jclass clazz, jint fd) { - int optval; - if (netty_unix_socket_getOption(env, fd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)) == -1) { - return -1; - } - return optval; -} - -static jint netty_epoll_native_getTcpNotSentLowAt(JNIEnv* env, jclass clazz, jint fd) { - int optval; - if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_NOTSENT_LOWAT, &optval, sizeof(optval)) == -1) { - return -1; - } - return optval; -} - -static jint netty_epoll_native_getTrafficClass(JNIEnv* env, jclass clazz, jint fd) { - int optval; - if (netty_unix_socket_getOption(env, fd, IPPROTO_IP, IP_TOS, &optval, sizeof(optval)) == -1) { - return -1; - } - return optval; -} - -static jint netty_epoll_native_isBroadcast(JNIEnv* env, jclass clazz, jint fd) { - int optval; - if (netty_unix_socket_getOption(env, fd, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval)) == -1) { - return -1; - } - return optval; -} - -static jint netty_epoll_native_getTcpKeepIdle(JNIEnv* env, jclass clazz, jint fd) { - int optval; - if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_KEEPIDLE, &optval, sizeof(optval)) == -1) { - return -1; - } - return optval; -} - -static jint netty_epoll_native_getTcpKeepIntvl(JNIEnv* env, jclass clazz, jint fd) { - int optval; - if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_KEEPINTVL, &optval, sizeof(optval)) == -1) { - return -1; - } - return optval; -} - -static jint netty_epoll_native_getTcpKeepCnt(JNIEnv* env, jclass clazz, jint fd) { - int optval; - if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_KEEPCNT, &optval, sizeof(optval)) == -1) { - return -1; - } - return optval; -} - -static jint netty_epoll_native_getTcpUserTimeout(JNIEnv* env, jclass clazz, jint fd) { - int optval; - if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &optval, sizeof(optval)) == -1) { - return -1; - } - return optval; -} - -static jint netty_epoll_Native_isIpFreeBind(JNIEnv* env, jclass clazz, jint fd) { - int optval; - if (netty_unix_socket_getOption(env, fd, IPPROTO_IP, IP_FREEBIND, &optval, sizeof(optval)) == -1) { - return -1; - } - return optval; -} - -static void netty_epoll_native_tcpInfo0(JNIEnv* env, jclass clazz, jint fd, jintArray array) { - struct tcp_info tcp_info; - if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_INFO, &tcp_info, sizeof(tcp_info)) == -1) { - return; - } - unsigned int cArray[32]; - cArray[0] = tcp_info.tcpi_state; - cArray[1] = tcp_info.tcpi_ca_state; - cArray[2] = tcp_info.tcpi_retransmits; - cArray[3] = tcp_info.tcpi_probes; - cArray[4] = tcp_info.tcpi_backoff; - cArray[5] = tcp_info.tcpi_options; - cArray[6] = tcp_info.tcpi_snd_wscale; - cArray[7] = tcp_info.tcpi_rcv_wscale; - cArray[8] = tcp_info.tcpi_rto; - cArray[9] = tcp_info.tcpi_ato; - cArray[10] = tcp_info.tcpi_snd_mss; - cArray[11] = tcp_info.tcpi_rcv_mss; - cArray[12] = tcp_info.tcpi_unacked; - cArray[13] = tcp_info.tcpi_sacked; - cArray[14] = tcp_info.tcpi_lost; - cArray[15] = tcp_info.tcpi_retrans; - cArray[16] = tcp_info.tcpi_fackets; - cArray[17] = tcp_info.tcpi_last_data_sent; - cArray[18] = tcp_info.tcpi_last_ack_sent; - cArray[19] = tcp_info.tcpi_last_data_recv; - cArray[20] = tcp_info.tcpi_last_ack_recv; - cArray[21] = tcp_info.tcpi_pmtu; - cArray[22] = tcp_info.tcpi_rcv_ssthresh; - cArray[23] = tcp_info.tcpi_rtt; - cArray[24] = tcp_info.tcpi_rttvar; - cArray[25] = tcp_info.tcpi_snd_ssthresh; - cArray[26] = tcp_info.tcpi_snd_cwnd; - cArray[27] = tcp_info.tcpi_advmss; - cArray[28] = tcp_info.tcpi_reordering; - cArray[29] = tcp_info.tcpi_rcv_rtt; - cArray[30] = tcp_info.tcpi_rcv_space; - cArray[31] = tcp_info.tcpi_total_retrans; - - (*env)->SetIntArrayRegion(env, array, 0, 32, cArray); -} static jstring netty_epoll_native_kernelVersion(JNIEnv* env, jclass clazz) { struct utsname name; @@ -627,14 +314,6 @@ static jstring netty_epoll_native_kernelVersion(JNIEnv* env, jclass clazz) { return NULL; } -static jint netty_epoll_native_iovMax(JNIEnv* env, jclass clazz) { - return IOV_MAX; -} - -static jint netty_epoll_native_uioMaxIov(JNIEnv* env, jclass clazz) { - return UIO_MAXIOV; -} - static jboolean netty_epoll_native_isSupportingSendmmsg(JNIEnv* env, jclass clazz) { if (sendmmsg) { return JNI_TRUE; @@ -699,10 +378,6 @@ static jint netty_epoll_native_splice0(JNIEnv* env, jclass clazz, jint fd, jlong return (jint) res; } -static jlong netty_epoll_native_ssizeMax(JNIEnv* env, jclass clazz) { - return SSIZE_MAX; -} - static jint netty_epoll_native_tcpMd5SigMaxKeyLen(JNIEnv* env, jclass clazz) { struct tcp_md5sig md5sig; @@ -713,43 +388,6 @@ static jint netty_epoll_native_tcpMd5SigMaxKeyLen(JNIEnv* env, jclass clazz) { return TCP_MD5SIG_MAXKEYLEN; } - -static void netty_epoll_native_setTcpMd5Sig0(JNIEnv* env, jclass clazz, jint fd, jbyteArray address, jint scopeId, jbyteArray key) { - struct sockaddr_storage addr; - if (netty_unix_socket_initSockaddr(env, address, scopeId, 0, &addr) == -1) { - return; - } - - struct tcp_md5sig md5sig; - memset(&md5sig, 0, sizeof(md5sig)); - md5sig.tcpm_addr.ss_family = addr.ss_family; - - struct sockaddr_in* ipaddr; - struct sockaddr_in6* ip6addr; - - switch (addr.ss_family) { - case AF_INET: - ipaddr = (struct sockaddr_in*) &addr; - memcpy(&((struct sockaddr_in *) &md5sig.tcpm_addr)->sin_addr, &ipaddr->sin_addr, sizeof(ipaddr->sin_addr)); - break; - case AF_INET6: - ip6addr = (struct sockaddr_in6*) &addr; - memcpy(&((struct sockaddr_in6 *) &md5sig.tcpm_addr)->sin6_addr, &ip6addr->sin6_addr, sizeof(ip6addr->sin6_addr)); - break; - } - - if (key != NULL) { - md5sig.tcpm_keylen = (*env)->GetArrayLength(env, key); - (*env)->GetByteArrayRegion(env, key, 0, md5sig.tcpm_keylen, (void *) &md5sig.tcpm_key); - if ((*env)->ExceptionCheck(env) == JNI_TRUE) { - return; - } - } - - if (setsockopt(fd, IPPROTO_TCP, TCP_MD5SIG, &md5sig, sizeof(md5sig)) < 0) { - netty_unix_errors_throwChannelExceptionErrorNo(env, "setsockopt() failed: ", errno); - } -} // JNI Registered Methods End // JNI Method Registration Table Begin @@ -759,10 +397,7 @@ static const JNINativeMethod statically_referenced_fixed_method_table[] = { { "epollout", "()I", (void *) netty_epoll_native_epollout }, { "epollrdhup", "()I", (void *) netty_epoll_native_epollrdhup }, { "epollerr", "()I", (void *) netty_epoll_native_epollerr }, - { "ssizeMax", "()J", (void *) netty_epoll_native_ssizeMax }, { "tcpMd5SigMaxKeyLen", "()I", (void *) netty_epoll_native_tcpMd5SigMaxKeyLen }, - { "iovMax", "()I", (void *) netty_epoll_native_iovMax }, - { "uioMaxIov", "()I", (void *) netty_epoll_native_uioMaxIov }, { "isSupportingSendmmsg", "()Z", (void *) netty_epoll_native_isSupportingSendmmsg }, { "isSupportingTcpFastopen", "()Z", (void *) netty_epoll_native_isSupportingTcpFastopen }, { "kernelVersion", "()Ljava/lang/String;", (void *) netty_epoll_native_kernelVersion } @@ -778,35 +413,10 @@ static const JNINativeMethod fixed_method_table[] = { { "epollCtlMod0", "(III)I", (void *) netty_epoll_native_epollCtlMod0 }, { "epollCtlDel0", "(II)I", (void *) netty_epoll_native_epollCtlDel0 }, // "sendmmsg0" has a dynamic signature - { "recvFd0", "(I)I", (void *) netty_epoll_native_recvFd0 }, - { "sendFd0", "(II)I", (void *) netty_epoll_native_sendFd0 }, // "sendFile0" has a dynamic signature - { "setReuseAddress", "(II)V", (void *) netty_epoll_native_setReuseAddress }, - { "setReusePort", "(II)V", (void *) netty_epoll_native_setReusePort }, - { "setTcpFastopen", "(II)V", (void *) netty_epoll_native_setTcpFastopen }, - { "setTcpNotSentLowAt", "(II)V", (void *) netty_epoll_native_setTcpNotSentLowAt }, - { "setTrafficClass", "(II)V", (void *) netty_epoll_native_setTrafficClass }, - { "setBroadcast", "(II)V", (void *) netty_epoll_native_setBroadcast }, - { "setTcpKeepIdle", "(II)V", (void *) netty_epoll_native_setTcpKeepIdle }, - { "setTcpKeepIntvl", "(II)V", (void *) netty_epoll_native_setTcpKeepIntvl }, - { "setTcpKeepCnt", "(II)V", (void *) netty_epoll_native_setTcpKeepCnt }, - { "setTcpUserTimeout", "(II)V", (void *) netty_epoll_native_setTcpUserTimeout }, - { "setIpFreeBind", "(II)V", (void *) netty_epoll_native_setIpFreeBind }, - { "isReuseAddress", "(I)I", (void *) netty_epoll_native_isReuseAddress }, - { "isReusePort", "(I)I", (void *) netty_epoll_native_isReusePort }, - { "getTcpNotSentLowAt", "(I)I", (void *) netty_epoll_native_getTcpNotSentLowAt }, - { "getTrafficClass", "(I)I", (void *) netty_epoll_native_getTrafficClass }, - { "isBroadcast", "(I)I", (void *) netty_epoll_native_isBroadcast }, - { "getTcpKeepIdle", "(I)I", (void *) netty_epoll_native_getTcpKeepIdle }, - { "getTcpKeepIntvl", "(I)I", (void *) netty_epoll_native_getTcpKeepIntvl }, - { "getTcpKeepCnt", "(I)I", (void *) netty_epoll_native_getTcpKeepCnt }, - { "getTcpUserTimeout", "(I)I", (void *) netty_epoll_native_getTcpUserTimeout }, - { "isIpFreeBind", "(I)I", (void *) netty_epoll_Native_isIpFreeBind }, - { "tcpInfo0", "(I[I)V", (void *) netty_epoll_native_tcpInfo0 }, { "sizeofEpollEvent", "()I", (void *) netty_epoll_native_sizeofEpollEvent }, { "offsetofEpollData", "()I", (void *) netty_epoll_native_offsetofEpollData }, - { "splice0", "(IJIJJ)I", (void *) netty_epoll_native_splice0 }, - { "setTcpMd5Sig0", "(I[BI[B)V", (void *) netty_epoll_native_setTcpMd5Sig0 } + { "splice0", "(IJIJJ)I", (void *) netty_epoll_native_splice0 } }; static const jint fixed_method_table_size = sizeof(fixed_method_table) / sizeof(fixed_method_table[0]); @@ -823,6 +433,7 @@ static JNINativeMethod* createDynamicMethodsTable(const char* packagePrefix) { dynamicMethod->signature = netty_unix_util_prepend("(I[L", dynamicTypeName); dynamicMethod->fnPtr = (void *) netty_epoll_native_sendmmsg0; free(dynamicTypeName); + ++dynamicMethod; dynamicTypeName = netty_unix_util_prepend(packagePrefix, "io/netty/channel/DefaultFileRegion;JJJ)J"); dynamicMethod->name = "sendfile0"; @@ -864,6 +475,9 @@ static jint netty_epoll_native_JNI_OnLoad(JNIEnv* env, const char* packagePrefix freeDynamicMethodsTable(dynamicMethods); dynamicMethods = NULL; // Load all c modules that we depend upon + if (netty_unix_limits_JNI_OnLoad(env, packagePrefix) == JNI_ERR) { + return JNI_ERR; + } if (netty_unix_errors_JNI_OnLoad(env, packagePrefix) == JNI_ERR) { return JNI_ERR; } @@ -873,6 +487,10 @@ static jint netty_epoll_native_JNI_OnLoad(JNIEnv* env, const char* packagePrefix if (netty_unix_socket_JNI_OnLoad(env, packagePrefix) == JNI_ERR) { return JNI_ERR; } + if (netty_epoll_linuxsocket_JNI_OnLoad(env, packagePrefix) == JNI_ERR) { + return JNI_ERR; + } + // Initialize this module char* nettyClassName = netty_unix_util_prepend(packagePrefix, "io/netty/channel/DefaultFileRegion"); jclass fileRegionCls = (*env)->FindClass(env, nettyClassName); @@ -950,7 +568,8 @@ static jint netty_epoll_native_JNI_OnLoad(JNIEnv* env, const char* packagePrefix return JNI_ERR; } - if (!initializeEpollWaitClock()) { + if (!netty_unix_util_initialize_wait_clock(&waitClockId)) { + fprintf(stderr, "FATAL: could not find a clock for clock_gettime!\n"); return JNI_ERR; } @@ -958,49 +577,11 @@ static jint netty_epoll_native_JNI_OnLoad(JNIEnv* env, const char* packagePrefix } static void netty_epoll_native_JNI_OnUnLoad(JNIEnv* env) { + netty_unix_limits_JNI_OnUnLoad(env); netty_unix_errors_JNI_OnUnLoad(env); netty_unix_filedescriptor_JNI_OnUnLoad(env); netty_unix_socket_JNI_OnUnLoad(env); -} - -/** - * The expected format of the library name is "lib<>netty-transport-native-epoll" where the <> portion is what we will return. - */ -static char* parsePackagePrefix(const char* libraryPathName, jint* status) { - char* packageNameEnd = strstr(libraryPathName, "netty-transport-native-epoll"); - if (packageNameEnd == NULL) { - *status = JNI_ERR; - return NULL; - } - char* packagePrefix = netty_unix_util_rstrstr(packageNameEnd, libraryPathName, "lib"); - if (packagePrefix == NULL) { - *status = JNI_ERR; - return NULL; - } - packagePrefix += 3; - if (packagePrefix == packageNameEnd) { - return NULL; - } - // packagePrefix length is > 0 - // Make a copy so we can modify the value without impacting libraryPathName. - size_t packagePrefixLen = packageNameEnd - packagePrefix; - packagePrefix = strndup(packagePrefix, packagePrefixLen); - // Make sure the packagePrefix is in the correct format for the JNI functions it will be used with. - char* temp = packagePrefix; - packageNameEnd = packagePrefix + packagePrefixLen; - // Package names must be sanitized, in JNI packages names are separated by '/' characters. - for (; temp != packageNameEnd; ++temp) { - if (*temp == '-') { - *temp = '/'; - } - } - // Make sure packagePrefix is terminated with the '/' JNI package separator. - if(*(--temp) != '/') { - temp = packagePrefix; - packagePrefix = netty_unix_util_prepend(packagePrefix, "/"); - free(temp); - } - return packagePrefix; + netty_epoll_linuxsocket_JNI_OnUnLoad(env); } jint JNI_OnLoad(JavaVM* vm, void* reserved) { @@ -1013,11 +594,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { jint status = 0; // We need to use an address of a function that is uniquely part of this library, so choose a static // function. See https://github.com/netty/netty/issues/4840. - if (!dladdr((void*) parsePackagePrefix, &dlinfo)) { + if (!dladdr((void*) netty_epoll_native_JNI_OnUnLoad, &dlinfo)) { fprintf(stderr, "FATAL: transport-native-epoll JNI call to dladdr failed!\n"); return JNI_ERR; } - char* packagePrefix = parsePackagePrefix(dlinfo.dli_fname, &status); + char* packagePrefix = netty_unix_util_parse_package_prefix(dlinfo.dli_fname, "netty-transport-native-epoll", &status); if (status == JNI_ERR) { fprintf(stderr, "FATAL: transport-native-epoll JNI encountered unexpected dlinfo.dli_fname: %s\n", dlinfo.dli_fname); return JNI_ERR; diff --git a/transport-native-epoll/src/main/c/netty_unix_util.c b/transport-native-epoll/src/main/c/netty_unix_util.c deleted file mode 100644 index 7b7fea19c9..0000000000 --- a/transport-native-epoll/src/main/c/netty_unix_util.c +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2016 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ - -#include -#include -#include "netty_unix_util.h" - -char* netty_unix_util_prepend(const char* prefix, const char* str) { - if (prefix == NULL) { - char* result = (char*) malloc(sizeof(char) * (strlen(str) + 1)); - strcpy(result, str); - return result; - } - char* result = (char*) malloc(sizeof(char) * (strlen(prefix) + strlen(str) + 1)); - strcpy(result, prefix); - strcat(result, str); - return result; -} - -char* netty_unix_util_rstrstr(char* s1rbegin, const char* s1rend, const char* s2) { - size_t s2len = strlen(s2); - char *s = s1rbegin - s2len; - - for (; s >= s1rend; --s) { - if (strncmp(s, s2, s2len) == 0) { - return s; - } - } - return NULL; -} - -jint netty_unix_util_register_natives(JNIEnv* env, const char* packagePrefix, const char* className, const JNINativeMethod* methods, jint numMethods) { - char* nettyClassName = netty_unix_util_prepend(packagePrefix, className); - jclass nativeCls = (*env)->FindClass(env, nettyClassName); - free(nettyClassName); - nettyClassName = NULL; - if (nativeCls == NULL) { - return JNI_ERR; - } - - return (*env)->RegisterNatives(env, nativeCls, methods, numMethods); -} diff --git a/transport-native-epoll/src/main/java/io/netty/channel/epoll/AbstractEpollChannel.java b/transport-native-epoll/src/main/java/io/netty/channel/epoll/AbstractEpollChannel.java index d6f5c4aae2..fc89fc3b68 100644 --- a/transport-native-epoll/src/main/java/io/netty/channel/epoll/AbstractEpollChannel.java +++ b/transport-native-epoll/src/main/java/io/netty/channel/epoll/AbstractEpollChannel.java @@ -28,6 +28,7 @@ import io.netty.channel.EventLoop; import io.netty.channel.RecvByteBufAllocator; import io.netty.channel.socket.ChannelInputShutdownEvent; import io.netty.channel.socket.ChannelInputShutdownReadComplete; +import io.netty.channel.unix.FileDescriptor; import io.netty.channel.unix.Socket; import io.netty.channel.unix.UnixChannel; import io.netty.util.ReferenceCountUtil; @@ -43,20 +44,20 @@ import static io.netty.util.internal.ObjectUtil.checkNotNull; abstract class AbstractEpollChannel extends AbstractChannel implements UnixChannel { private static final ChannelMetadata METADATA = new ChannelMetadata(false); private final int readFlag; - private final Socket fileDescriptor; + final LinuxSocket socket; protected int flags = Native.EPOLLET; boolean inputClosedSeenErrorOnRead; boolean epollInReadyRunnablePending; protected volatile boolean active; - AbstractEpollChannel(Socket fd, int flag) { + AbstractEpollChannel(LinuxSocket fd, int flag) { this(null, fd, flag, false); } - AbstractEpollChannel(Channel parent, Socket fd, int flag, boolean active) { + AbstractEpollChannel(Channel parent, LinuxSocket fd, int flag, boolean active) { super(parent); - fileDescriptor = checkNotNull(fd, "fd"); + socket = checkNotNull(fd, "fd"); readFlag = flag; flags |= flag; this.active = active; @@ -89,8 +90,8 @@ abstract class AbstractEpollChannel extends AbstractChannel implements UnixChann } @Override - public final Socket fd() { - return fileDescriptor; + public final FileDescriptor fd() { + return socket; } @Override @@ -115,7 +116,7 @@ abstract class AbstractEpollChannel extends AbstractChannel implements UnixChann try { doDeregister(); } finally { - fileDescriptor.close(); + socket.close(); } } @@ -131,7 +132,7 @@ abstract class AbstractEpollChannel extends AbstractChannel implements UnixChann @Override public boolean isOpen() { - return fileDescriptor.isOpen(); + return socket.isOpen(); } @Override @@ -158,7 +159,7 @@ abstract class AbstractEpollChannel extends AbstractChannel implements UnixChann } final boolean shouldBreakEpollInReady(ChannelConfig config) { - return fileDescriptor.isInputShutdown() && (inputClosedSeenErrorOnRead || !isAllowHalfClosure(config)); + return socket.isInputShutdown() && (inputClosedSeenErrorOnRead || !isAllowHalfClosure(config)); } final boolean isAllowHalfClosure(ChannelConfig config) { @@ -265,10 +266,10 @@ abstract class AbstractEpollChannel extends AbstractChannel implements UnixChann int localReadAmount; unsafe().recvBufAllocHandle().attemptedBytesRead(byteBuf.writableBytes()); if (byteBuf.hasMemoryAddress()) { - localReadAmount = fileDescriptor.readAddress(byteBuf.memoryAddress(), writerIndex, byteBuf.capacity()); + localReadAmount = socket.readAddress(byteBuf.memoryAddress(), writerIndex, byteBuf.capacity()); } else { ByteBuffer buf = byteBuf.internalNioBuffer(writerIndex, byteBuf.writableBytes()); - localReadAmount = fileDescriptor.read(buf, buf.position(), buf.limit()); + localReadAmount = socket.read(buf, buf.position(), buf.limit()); } if (localReadAmount > 0) { byteBuf.writerIndex(writerIndex + localReadAmount); @@ -283,8 +284,8 @@ abstract class AbstractEpollChannel extends AbstractChannel implements UnixChann long memoryAddress = buf.memoryAddress(); int readerIndex = buf.readerIndex(); int writerIndex = buf.writerIndex(); - for (int i = writeSpinCount - 1; i >= 0; i--) { - int localFlushedAmount = fileDescriptor.writeAddress(memoryAddress, readerIndex, writerIndex); + for (int i = writeSpinCount; i > 0; --i) { + int localFlushedAmount = socket.writeAddress(memoryAddress, readerIndex, writerIndex); if (localFlushedAmount > 0) { writtenBytes += localFlushedAmount; if (writtenBytes == readableBytes) { @@ -302,10 +303,10 @@ abstract class AbstractEpollChannel extends AbstractChannel implements UnixChann } else { nioBuf = buf.nioBuffer(); } - for (int i = writeSpinCount - 1; i >= 0; i--) { + for (int i = writeSpinCount; i > 0; --i) { int pos = nioBuf.position(); int limit = nioBuf.limit(); - int localFlushedAmount = fileDescriptor.write(nioBuf, pos, limit); + int localFlushedAmount = socket.write(nioBuf, pos, limit); if (localFlushedAmount > 0) { nioBuf.position(pos + localFlushedAmount); writtenBytes += localFlushedAmount; @@ -410,10 +411,10 @@ abstract class AbstractEpollChannel extends AbstractChannel implements UnixChann * Shutdown the input side of the channel. */ void shutdownInput(boolean rdHup) { - if (!fd().isInputShutdown()) { + if (!socket.isInputShutdown()) { if (isAllowHalfClosure(config())) { try { - fd().shutdown(true, false); + socket.shutdown(true, false); } catch (IOException ignored) { // We attempted to shutdown and failed, which means the input has already effectively been // shutdown. @@ -422,9 +423,8 @@ abstract class AbstractEpollChannel extends AbstractChannel implements UnixChann } catch (NotYetConnectedException ignore) { // We attempted to shutdown and failed, which means the input has already effectively been // shutdown. - fireEventAndClose(ChannelInputShutdownEvent.INSTANCE); - return; } + clearEpollIn(); pipeline().fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE); } else { close(voidPromise()); @@ -471,7 +471,7 @@ abstract class AbstractEpollChannel extends AbstractChannel implements UnixChann * Called once a EPOLLOUT event is ready to be processed */ void epollOutReady() { - if (fd().isOutputShutdown()) { + if (socket.isOutputShutdown()) { return; } // directly call super.flush0() to force a flush now diff --git a/transport-native-epoll/src/main/java/io/netty/channel/epoll/AbstractEpollServerChannel.java b/transport-native-epoll/src/main/java/io/netty/channel/epoll/AbstractEpollServerChannel.java index 02401a58e5..798878f1dd 100644 --- a/transport-native-epoll/src/main/java/io/netty/channel/epoll/AbstractEpollServerChannel.java +++ b/transport-native-epoll/src/main/java/io/netty/channel/epoll/AbstractEpollServerChannel.java @@ -23,8 +23,6 @@ import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPromise; import io.netty.channel.EventLoop; import io.netty.channel.ServerChannel; -import io.netty.channel.unix.FileDescriptor; -import io.netty.channel.unix.Socket; import java.net.InetSocketAddress; import java.net.SocketAddress; @@ -32,31 +30,15 @@ import java.net.SocketAddress; public abstract class AbstractEpollServerChannel extends AbstractEpollChannel implements ServerChannel { private static final ChannelMetadata METADATA = new ChannelMetadata(false, 16); - /** - * @deprecated Use {@link #AbstractEpollServerChannel(Socket, boolean)}. - */ - @Deprecated protected AbstractEpollServerChannel(int fd) { - this(new Socket(fd), false); + this(new LinuxSocket(fd), false); } - /** - * @deprecated Use {@link #AbstractEpollServerChannel(Socket, boolean)}. - */ - @Deprecated - protected AbstractEpollServerChannel(FileDescriptor fd) { - this(new Socket(fd.intValue())); - } - - /** - * @deprecated Use {@link #AbstractEpollServerChannel(Socket, boolean)}. - */ - @Deprecated - protected AbstractEpollServerChannel(Socket fd) { + AbstractEpollServerChannel(LinuxSocket fd) { this(fd, isSoErrorZero(fd)); } - protected AbstractEpollServerChannel(Socket fd, boolean active) { + AbstractEpollServerChannel(LinuxSocket fd, boolean active) { super(null, fd, Native.EPOLLIN, active); } @@ -117,6 +99,7 @@ public abstract class AbstractEpollServerChannel extends AbstractEpollChannel im final ChannelPipeline pipeline = pipeline(); allocHandle.reset(config); + allocHandle.attemptedBytesRead(1); epollInBefore(); Throwable exception = null; @@ -126,16 +109,16 @@ public abstract class AbstractEpollServerChannel extends AbstractEpollChannel im // lastBytesRead represents the fd. We use lastBytesRead because it must be set so that the // EpollRecvByteAllocatorHandle knows if it should try to read again or not when autoRead is // enabled. - allocHandle.lastBytesRead(fd().accept(acceptedAddress)); + allocHandle.lastBytesRead(socket.accept(acceptedAddress)); if (allocHandle.lastBytesRead() == -1) { // this means everything was handled for now break; } allocHandle.incMessagesRead(1); - int len = acceptedAddress[0]; readPending = false; - pipeline.fireChannelRead(newChildChannel(allocHandle.lastBytesRead(), acceptedAddress, 1, len)); + pipeline.fireChannelRead(newChildChannel(allocHandle.lastBytesRead(), acceptedAddress, 1, + acceptedAddress[0])); } while (allocHandle.continueReading()); } catch (Throwable t) { exception = t; diff --git a/transport-native-epoll/src/main/java/io/netty/channel/epoll/AbstractEpollStreamChannel.java b/transport-native-epoll/src/main/java/io/netty/channel/epoll/AbstractEpollStreamChannel.java index 6c6e5988af..6c3efe869e 100644 --- a/transport-native-epoll/src/main/java/io/netty/channel/epoll/AbstractEpollStreamChannel.java +++ b/transport-native-epoll/src/main/java/io/netty/channel/epoll/AbstractEpollStreamChannel.java @@ -17,9 +17,7 @@ package io.netty.channel.epoll; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.ByteBufUtil; import io.netty.buffer.CompositeByteBuf; -import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelConfig; import io.netty.channel.ChannelFuture; @@ -35,7 +33,8 @@ import io.netty.channel.FileRegion; import io.netty.channel.RecvByteBufAllocator; import io.netty.channel.socket.DuplexChannel; import io.netty.channel.unix.FileDescriptor; -import io.netty.channel.unix.Socket; +import io.netty.channel.unix.IovArray; +import io.netty.channel.unix.SocketWritableByteChannel; import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.StringUtil; import io.netty.util.internal.ThrowableUtil; @@ -54,6 +53,7 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import static io.netty.channel.unix.FileDescriptor.pipe; +import static io.netty.channel.unix.Limits.IOV_MAX; import static io.netty.util.internal.ObjectUtil.checkNotNull; public abstract class AbstractEpollStreamChannel extends AbstractEpollChannel implements DuplexChannel { @@ -89,45 +89,25 @@ public abstract class AbstractEpollStreamChannel extends AbstractEpollChannel im private WritableByteChannel byteChannel; - /** - * @deprecated Use {@link #AbstractEpollStreamChannel(Channel, Socket)}. - */ - @Deprecated protected AbstractEpollStreamChannel(Channel parent, int fd) { - this(parent, new Socket(fd)); + this(parent, new LinuxSocket(fd)); } - /** - * @deprecated Use {@link #AbstractEpollStreamChannel(Socket, boolean)}. - */ - @Deprecated protected AbstractEpollStreamChannel(int fd) { - this(new Socket(fd)); + this(new LinuxSocket(fd)); } - /** - * @deprecated Use {@link #AbstractEpollStreamChannel(Socket, boolean)}. - */ - @Deprecated - protected AbstractEpollStreamChannel(FileDescriptor fd) { - this(new Socket(fd.intValue())); - } - - /** - * @deprecated Use {@link #AbstractEpollStreamChannel(Socket, boolean)}. - */ - @Deprecated - protected AbstractEpollStreamChannel(Socket fd) { + AbstractEpollStreamChannel(LinuxSocket fd) { this(fd, isSoErrorZero(fd)); } - protected AbstractEpollStreamChannel(Channel parent, Socket fd) { + AbstractEpollStreamChannel(Channel parent, LinuxSocket fd) { super(parent, fd, Native.EPOLLIN, true); // Add EPOLLRDHUP so we are notified once the remote peer close the connection. flags |= Native.EPOLLRDHUP; } - protected AbstractEpollStreamChannel(Socket fd, boolean active) { + protected AbstractEpollStreamChannel(LinuxSocket fd, boolean active) { super(null, fd, Native.EPOLLIN, active); // Add EPOLLRDHUP so we are notified once the remote peer close the connection. flags |= Native.EPOLLRDHUP; @@ -301,8 +281,8 @@ public abstract class AbstractEpollStreamChannel extends AbstractEpollChannel im boolean done = false; int offset = 0; int end = offset + cnt; - for (int i = writeSpinCount - 1; i >= 0; i--) { - long localWrittenBytes = fd().writevAddresses(array.memoryAddress(offset), cnt); + for (int i = writeSpinCount; i > 0; --i) { + long localWrittenBytes = socket.writevAddresses(array.memoryAddress(offset), cnt); if (localWrittenBytes == 0) { break; } @@ -340,8 +320,8 @@ public abstract class AbstractEpollStreamChannel extends AbstractEpollChannel im boolean done = false; int offset = 0; int end = offset + nioBufferCnt; - for (int i = writeSpinCount - 1; i >= 0; i--) { - long localWrittenBytes = fd().writev(nioBuffers, offset, nioBufferCnt); + for (int i = writeSpinCount; i > 0; --i) { + long localWrittenBytes = socket.writev(nioBuffers, offset, nioBufferCnt); if (localWrittenBytes == 0) { break; } @@ -390,10 +370,10 @@ public abstract class AbstractEpollStreamChannel extends AbstractEpollChannel im boolean done = false; long flushedAmount = 0; - for (int i = writeSpinCount - 1; i >= 0; i--) { + for (int i = writeSpinCount; i > 0; --i) { final long offset = region.transferred(); final long localFlushedAmount = - Native.sendfile(fd().intValue(), region, baseOffset, offset, regionCount - offset); + Native.sendfile(socket.intValue(), region, baseOffset, offset, regionCount - offset); if (localFlushedAmount == 0) { break; } @@ -426,9 +406,9 @@ public abstract class AbstractEpollStreamChannel extends AbstractEpollChannel im long flushedAmount = 0; if (byteChannel == null) { - byteChannel = new SocketWritableByteChannel(); + byteChannel = new EpollSocketWritableByteChannel(); } - for (int i = writeSpinCount - 1; i >= 0; i--) { + for (int i = writeSpinCount; i > 0; --i) { final long localFlushedAmount = region.transferTo(byteChannel, region.transferred()); if (localFlushedAmount == 0) { break; @@ -564,7 +544,7 @@ public abstract class AbstractEpollStreamChannel extends AbstractEpollChannel im // Special handling of CompositeByteBuf to reduce memory copies if some of the Components // in the CompositeByteBuf are backed by a memoryAddress. CompositeByteBuf comp = (CompositeByteBuf) buf; - if (!comp.isDirect() || comp.nioBufferCount() > Native.IOV_MAX) { + if (!comp.isDirect() || comp.nioBufferCount() > IOV_MAX) { // more then 1024 buffers for gathering writes so just do a memory copy. buf = newDirectBuffer(buf); assert buf.hasMemoryAddress(); @@ -589,7 +569,7 @@ public abstract class AbstractEpollStreamChannel extends AbstractEpollChannel im private void shutdownOutput0(final ChannelPromise promise) { try { - fd().shutdown(false, true); + socket.shutdown(false, true); promise.setSuccess(); } catch (Throwable cause) { promise.setFailure(cause); @@ -598,7 +578,7 @@ public abstract class AbstractEpollStreamChannel extends AbstractEpollChannel im private void shutdownInput0(final ChannelPromise promise) { try { - fd().shutdown(true, false); + socket.shutdown(true, false); promise.setSuccess(); } catch (Throwable cause) { promise.setFailure(cause); @@ -607,7 +587,7 @@ public abstract class AbstractEpollStreamChannel extends AbstractEpollChannel im private void shutdown0(final ChannelPromise promise) { try { - fd().shutdown(true, true); + socket.shutdown(true, true); promise.setSuccess(); } catch (Throwable cause) { promise.setFailure(cause); @@ -616,17 +596,17 @@ public abstract class AbstractEpollStreamChannel extends AbstractEpollChannel im @Override public boolean isOutputShutdown() { - return fd().isOutputShutdown(); + return socket.isOutputShutdown(); } @Override public boolean isInputShutdown() { - return fd().isInputShutdown(); + return socket.isInputShutdown(); } @Override public boolean isShutdown() { - return fd().isShutdown(); + return socket.isShutdown(); } @Override @@ -764,12 +744,12 @@ public abstract class AbstractEpollStreamChannel extends AbstractEpollChannel im */ protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception { if (localAddress != null) { - fd().bind(localAddress); + socket.bind(localAddress); } boolean success = false; try { - boolean connected = fd().connect(remoteAddress); + boolean connected = socket.connect(remoteAddress); if (!connected) { setFlag(Native.EPOLLOUT); } @@ -952,7 +932,7 @@ public abstract class AbstractEpollStreamChannel extends AbstractEpollChannel im * Finish the connect */ boolean doFinishConnect() throws Exception { - if (fd().finishConnect()) { + if (socket.finishConnect()) { clearFlag(Native.EPOLLOUT); return true; } else { @@ -1085,7 +1065,7 @@ public abstract class AbstractEpollStreamChannel extends AbstractEpollChannel im int splicedIn = 0; for (;;) { // Splicing until there is nothing left to splice. - int localSplicedIn = Native.splice(fd().intValue(), -1, pipeOut.intValue(), -1, length); + int localSplicedIn = Native.splice(socket.intValue(), -1, pipeOut.intValue(), -1, length); if (localSplicedIn == 0) { break; } @@ -1185,7 +1165,7 @@ public abstract class AbstractEpollStreamChannel extends AbstractEpollChannel im public boolean spliceOut() throws Exception { assert ch.eventLoop().inEventLoop(); try { - int splicedOut = Native.splice(ch.pipeIn.intValue(), -1, ch.fd().intValue(), -1, len); + int splicedOut = Native.splice(ch.pipeIn.intValue(), -1, ch.socket.intValue(), -1, len); len -= splicedOut; if (len == 0) { if (autoRead) { @@ -1257,55 +1237,14 @@ public abstract class AbstractEpollStreamChannel extends AbstractEpollChannel im } } - private final class SocketWritableByteChannel implements WritableByteChannel { - - @Override - public int write(ByteBuffer src) throws IOException { - final int written; - int position = src.position(); - int limit = src.limit(); - if (src.isDirect()) { - written = fd().write(src, position, src.limit()); - } else { - final int readableBytes = limit - position; - ByteBuf buffer = null; - try { - if (readableBytes == 0) { - buffer = Unpooled.EMPTY_BUFFER; - } else { - final ByteBufAllocator alloc = alloc(); - if (alloc.isDirectBufferPooled()) { - buffer = alloc.directBuffer(readableBytes); - } else { - buffer = ByteBufUtil.threadLocalDirectBuffer(); - if (buffer == null) { - buffer = Unpooled.directBuffer(readableBytes); - } - } - } - buffer.writeBytes(src.duplicate()); - ByteBuffer nioBuffer = buffer.internalNioBuffer(buffer.readerIndex(), readableBytes); - written = fd().write(nioBuffer, nioBuffer.position(), nioBuffer.limit()); - } finally { - if (buffer != null) { - buffer.release(); - } - } - } - if (written > 0) { - src.position(position + written); - } - return written; + private final class EpollSocketWritableByteChannel extends SocketWritableByteChannel { + EpollSocketWritableByteChannel() { + super(socket); } @Override - public boolean isOpen() { - return fd().isOpen(); - } - - @Override - public void close() throws IOException { - fd().close(); + protected ByteBufAllocator alloc() { + return AbstractEpollStreamChannel.this.alloc(); } } } diff --git a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollChannelConfig.java b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollChannelConfig.java index e18e8c19c1..8aae70c600 100644 --- a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollChannelConfig.java +++ b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollChannelConfig.java @@ -22,7 +22,6 @@ import io.netty.channel.DefaultChannelConfig; import io.netty.channel.MessageSizeEstimator; import io.netty.channel.RecvByteBufAllocator; import io.netty.channel.WriteBufferWaterMark; - import java.io.IOException; import java.util.Map; 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 74e1ca5fdf..2f0a80a747 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 @@ -16,15 +16,13 @@ package io.netty.channel.epoll; import io.netty.channel.ChannelOption; -import io.netty.channel.unix.DomainSocketReadMode; - +import io.netty.channel.unix.UnixChannelOption; import java.net.InetAddress; import java.util.Map; -public final class EpollChannelOption extends ChannelOption { +public final class EpollChannelOption extends UnixChannelOption { public static final ChannelOption TCP_CORK = valueOf(EpollChannelOption.class, "TCP_CORK"); - public static final ChannelOption SO_REUSEPORT = valueOf(EpollChannelOption.class, "SO_REUSEPORT"); public static final ChannelOption TCP_NOTSENT_LOWAT = valueOf(EpollChannelOption.class, "TCP_NOTSENT_LOWAT"); public static final ChannelOption TCP_KEEPIDLE = valueOf(EpollChannelOption.class, "TCP_KEEPIDLE"); public static final ChannelOption TCP_KEEPINTVL = valueOf(EpollChannelOption.class, "TCP_KEEPINTVL"); @@ -37,8 +35,6 @@ public final class EpollChannelOption extends ChannelOption { ChannelOption.valueOf(EpollChannelOption.class, "TCP_DEFER_ACCEPT"); public static final ChannelOption TCP_QUICKACK = valueOf(EpollChannelOption.class, "TCP_QUICKACK"); - public static final ChannelOption DOMAIN_SOCKET_READ_MODE = - ChannelOption.valueOf(EpollChannelOption.class, "DOMAIN_SOCKET_READ_MODE"); public static final ChannelOption EPOLL_MODE = ChannelOption.valueOf(EpollChannelOption.class, "EPOLL_MODE"); @@ -46,6 +42,5 @@ public final class EpollChannelOption extends ChannelOption { @SuppressWarnings({ "unused", "deprecation" }) private EpollChannelOption() { - super(null); } } diff --git a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollDatagramChannel.java b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollDatagramChannel.java index 4569ca0fbe..eb5d367b60 100644 --- a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollDatagramChannel.java +++ b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollDatagramChannel.java @@ -29,8 +29,7 @@ import io.netty.channel.socket.DatagramChannel; import io.netty.channel.socket.DatagramChannelConfig; import io.netty.channel.socket.DatagramPacket; import io.netty.channel.unix.DatagramSocketAddress; -import io.netty.channel.unix.FileDescriptor; -import io.netty.channel.unix.Socket; +import io.netty.channel.unix.IovArray; import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.StringUtil; @@ -45,7 +44,8 @@ import java.nio.channels.NotYetConnectedException; import java.util.ArrayList; import java.util.List; -import static io.netty.channel.unix.Socket.newSocketDgram; +import static io.netty.channel.epoll.LinuxSocket.newSocketDgram; +import static io.netty.channel.unix.Limits.IOV_MAX; /** * {@link DatagramChannel} implementation that uses linux EPOLL Edge-Triggered Mode for @@ -70,15 +70,11 @@ public final class EpollDatagramChannel extends AbstractEpollChannel implements config = new EpollDatagramChannelConfig(this); } - /** - * @deprecated Use {@link #EpollDatagramChannel(Socket)}. - */ - @Deprecated - public EpollDatagramChannel(FileDescriptor fd) { - this(new Socket(fd.intValue())); + public EpollDatagramChannel(int fd) { + this(new LinuxSocket(fd)); } - public EpollDatagramChannel(Socket fd) { + EpollDatagramChannel(LinuxSocket fd) { super(null, fd, Native.EPOLLIN, true); // As we create an EpollDatagramChannel from a FileDescriptor we should try to obtain the remote and local // address from it. This is needed as the FileDescriptor may be bound already. @@ -104,7 +100,7 @@ public final class EpollDatagramChannel extends AbstractEpollChannel implements @Override @SuppressWarnings("deprecation") public boolean isActive() { - return fd().isOpen() && (config.getActiveOnOpen() && isRegistered() || active); + return socket.isOpen() && (config.getActiveOnOpen() && isRegistered() || active); } @Override @@ -280,8 +276,8 @@ public final class EpollDatagramChannel extends AbstractEpollChannel implements protected void doBind(SocketAddress localAddress) throws Exception { InetSocketAddress addr = (InetSocketAddress) localAddress; checkResolvable(addr); - fd().bind(addr); - local = fd().localAddress(); + socket.bind(addr); + local = socket.localAddress(); active = true; } @@ -307,7 +303,7 @@ public final class EpollDatagramChannel extends AbstractEpollChannel implements NativeDatagramPacketArray.NativeDatagramPacket[] packets = array.packets(); while (cnt > 0) { - int send = Native.sendmmsg(fd().intValue(), packets, offset, cnt); + int send = Native.sendmmsg(socket.intValue(), packets, offset, cnt); if (send == 0) { // Did not write all messages. setFlag(Native.EPOLLOUT); @@ -323,7 +319,7 @@ public final class EpollDatagramChannel extends AbstractEpollChannel implements } } boolean done = false; - for (int i = config().getWriteSpinCount() - 1; i >= 0; i--) { + for (int i = config().getWriteSpinCount(); i > 0; --i) { if (doWriteMessage(msg)) { done = true; break; @@ -375,7 +371,7 @@ public final class EpollDatagramChannel extends AbstractEpollChannel implements final int writtenBytes; if (data.hasMemoryAddress()) { long memoryAddress = data.memoryAddress(); - writtenBytes = fd().sendToAddress(memoryAddress, data.readerIndex(), data.writerIndex(), + writtenBytes = socket.sendToAddress(memoryAddress, data.readerIndex(), data.writerIndex(), remoteAddress.getAddress(), remoteAddress.getPort()); } else if (data instanceof CompositeByteBuf) { IovArray array = ((EpollEventLoop) eventLoop()).cleanArray(); @@ -383,11 +379,11 @@ public final class EpollDatagramChannel extends AbstractEpollChannel implements int cnt = array.count(); assert cnt != 0; - writtenBytes = fd().sendToAddresses(array.memoryAddress(0), + writtenBytes = socket.sendToAddresses(array.memoryAddress(0), cnt, remoteAddress.getAddress(), remoteAddress.getPort()); } else { ByteBuffer nioData = data.internalNioBuffer(data.readerIndex(), data.readableBytes()); - writtenBytes = fd().sendTo(nioData, nioData.position(), nioData.limit(), + writtenBytes = socket.sendTo(nioData, nioData.position(), nioData.limit(), remoteAddress.getAddress(), remoteAddress.getPort()); } @@ -407,7 +403,7 @@ public final class EpollDatagramChannel extends AbstractEpollChannel implements // Special handling of CompositeByteBuf to reduce memory copies if some of the Components // in the CompositeByteBuf are backed by a memoryAddress. CompositeByteBuf comp = (CompositeByteBuf) content; - if (comp.isDirect() && comp.nioBufferCount() <= Native.IOV_MAX) { + if (comp.isDirect() && comp.nioBufferCount() <= IOV_MAX) { return msg; } } @@ -423,7 +419,7 @@ public final class EpollDatagramChannel extends AbstractEpollChannel implements // Special handling of CompositeByteBuf to reduce memory copies if some of the Components // in the CompositeByteBuf are backed by a memoryAddress. CompositeByteBuf comp = (CompositeByteBuf) buf; - if (!comp.isDirect() || comp.nioBufferCount() > Native.IOV_MAX) { + if (!comp.isDirect() || comp.nioBufferCount() > IOV_MAX) { // more then 1024 buffers for gathering writes so just do a memory copy. buf = newDirectBuffer(buf); assert buf.hasMemoryAddress(); @@ -452,7 +448,7 @@ public final class EpollDatagramChannel extends AbstractEpollChannel implements // Special handling of CompositeByteBuf to reduce memory copies if some of the Components // in the CompositeByteBuf are backed by a memoryAddress. CompositeByteBuf comp = (CompositeByteBuf) content; - if (comp.isDirect() && comp.nioBufferCount() <= Native.IOV_MAX) { + if (comp.isDirect() && comp.nioBufferCount() <= IOV_MAX) { return e; } } @@ -494,7 +490,7 @@ public final class EpollDatagramChannel extends AbstractEpollChannel implements checkResolvable(remoteAddress); EpollDatagramChannel.this.remote = remoteAddress; - EpollDatagramChannel.this.local = fd().localAddress(); + EpollDatagramChannel.this.local = socket.localAddress(); success = true; // First notify the promise before notifying the handler. @@ -543,11 +539,11 @@ public final class EpollDatagramChannel extends AbstractEpollChannel implements final DatagramSocketAddress remoteAddress; if (data.hasMemoryAddress()) { // has a memory address so use optimized call - remoteAddress = fd().recvFromAddress(data.memoryAddress(), data.writerIndex(), + remoteAddress = socket.recvFromAddress(data.memoryAddress(), data.writerIndex(), data.capacity()); } else { ByteBuffer nioData = data.internalNioBuffer(data.writerIndex(), data.writableBytes()); - remoteAddress = fd().recvFrom(nioData, nioData.position(), nioData.limit()); + remoteAddress = socket.recvFrom(nioData, nioData.position(), nioData.limit()); } if (remoteAddress == null) { diff --git a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollDatagramChannelConfig.java b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollDatagramChannelConfig.java index c53feb28a8..ddb206551d 100644 --- a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollDatagramChannelConfig.java +++ b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollDatagramChannelConfig.java @@ -23,7 +23,6 @@ import io.netty.channel.MessageSizeEstimator; import io.netty.channel.RecvByteBufAllocator; import io.netty.channel.WriteBufferWaterMark; import io.netty.channel.socket.DatagramChannelConfig; - import java.io.IOException; import java.net.InetAddress; import java.net.NetworkInterface; @@ -208,7 +207,7 @@ public final class EpollDatagramChannelConfig extends EpollChannelConfig impleme @Override public int getSendBufferSize() { try { - return datagramChannel.fd().getSendBufferSize(); + return datagramChannel.socket.getSendBufferSize(); } catch (IOException e) { throw new ChannelException(e); } @@ -217,7 +216,7 @@ public final class EpollDatagramChannelConfig extends EpollChannelConfig impleme @Override public EpollDatagramChannelConfig setSendBufferSize(int sendBufferSize) { try { - datagramChannel.fd().setSendBufferSize(sendBufferSize); + datagramChannel.socket.setSendBufferSize(sendBufferSize); return this; } catch (IOException e) { throw new ChannelException(e); @@ -227,7 +226,7 @@ public final class EpollDatagramChannelConfig extends EpollChannelConfig impleme @Override public int getReceiveBufferSize() { try { - return datagramChannel.fd().getReceiveBufferSize(); + return datagramChannel.socket.getReceiveBufferSize(); } catch (IOException e) { throw new ChannelException(e); } @@ -236,7 +235,7 @@ public final class EpollDatagramChannelConfig extends EpollChannelConfig impleme @Override public EpollDatagramChannelConfig setReceiveBufferSize(int receiveBufferSize) { try { - datagramChannel.fd().setReceiveBufferSize(receiveBufferSize); + datagramChannel.socket.setReceiveBufferSize(receiveBufferSize); return this; } catch (IOException e) { throw new ChannelException(e); @@ -246,7 +245,7 @@ public final class EpollDatagramChannelConfig extends EpollChannelConfig impleme @Override public int getTrafficClass() { try { - return Native.getTrafficClass(datagramChannel.fd().intValue()); + return datagramChannel.socket.getTrafficClass(); } catch (IOException e) { throw new ChannelException(e); } @@ -255,7 +254,7 @@ public final class EpollDatagramChannelConfig extends EpollChannelConfig impleme @Override public EpollDatagramChannelConfig setTrafficClass(int trafficClass) { try { - Native.setTrafficClass(datagramChannel.fd().intValue(), trafficClass); + datagramChannel.socket.setTrafficClass(trafficClass); return this; } catch (IOException e) { throw new ChannelException(e); @@ -265,7 +264,7 @@ public final class EpollDatagramChannelConfig extends EpollChannelConfig impleme @Override public boolean isReuseAddress() { try { - return Native.isReuseAddress(datagramChannel.fd().intValue()) == 1; + return datagramChannel.socket.isReuseAddress(); } catch (IOException e) { throw new ChannelException(e); } @@ -274,7 +273,7 @@ public final class EpollDatagramChannelConfig extends EpollChannelConfig impleme @Override public EpollDatagramChannelConfig setReuseAddress(boolean reuseAddress) { try { - Native.setReuseAddress(datagramChannel.fd().intValue(), reuseAddress ? 1 : 0); + datagramChannel.socket.setReuseAddress(reuseAddress); return this; } catch (IOException e) { throw new ChannelException(e); @@ -284,7 +283,7 @@ public final class EpollDatagramChannelConfig extends EpollChannelConfig impleme @Override public boolean isBroadcast() { try { - return Native.isBroadcast(datagramChannel.fd().intValue()) == 1; + return datagramChannel.socket.isBroadcast(); } catch (IOException e) { throw new ChannelException(e); } @@ -293,7 +292,7 @@ public final class EpollDatagramChannelConfig extends EpollChannelConfig impleme @Override public EpollDatagramChannelConfig setBroadcast(boolean broadcast) { try { - Native.setBroadcast(datagramChannel.fd().intValue(), broadcast ? 1 : 0); + datagramChannel.socket.setBroadcast(broadcast); return this; } catch (IOException e) { throw new ChannelException(e); @@ -351,7 +350,7 @@ public final class EpollDatagramChannelConfig extends EpollChannelConfig impleme */ public boolean isReusePort() { try { - return Native.isReusePort(datagramChannel.fd().intValue()) == 1; + return datagramChannel.socket.isReusePort(); } catch (IOException e) { throw new ChannelException(e); } @@ -366,7 +365,7 @@ public final class EpollDatagramChannelConfig extends EpollChannelConfig impleme */ public EpollDatagramChannelConfig setReusePort(boolean reusePort) { try { - Native.setReusePort(datagramChannel.fd().intValue(), reusePort ? 1 : 0); + datagramChannel.socket.setReusePort(reusePort); return this; } catch (IOException e) { throw new ChannelException(e); diff --git a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollDomainSocketChannel.java b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollDomainSocketChannel.java index bc283034e0..05cdda3357 100644 --- a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollDomainSocketChannel.java +++ b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollDomainSocketChannel.java @@ -23,12 +23,12 @@ import io.netty.channel.unix.DomainSocketAddress; import io.netty.channel.unix.DomainSocketChannel; import io.netty.channel.unix.FileDescriptor; import io.netty.channel.unix.PeerCredentials; -import io.netty.channel.unix.Socket; +import io.netty.util.internal.UnstableApi; +import java.io.IOException; import java.net.SocketAddress; -import static io.netty.channel.unix.Socket.newSocketDomain; -import java.io.IOException; +import static io.netty.channel.epoll.LinuxSocket.newSocketDomain; public final class EpollDomainSocketChannel extends AbstractEpollStreamChannel implements DomainSocketChannel { private final EpollDomainSocketChannelConfig config = new EpollDomainSocketChannelConfig(this); @@ -40,33 +40,20 @@ public final class EpollDomainSocketChannel extends AbstractEpollStreamChannel i super(newSocketDomain(), false); } - /** - * @deprecated Use {@link #EpollDomainSocketChannel(Channel, Socket)}. - */ - @Deprecated - public EpollDomainSocketChannel(Channel parent, FileDescriptor fd) { - super(parent, new Socket(fd.intValue())); + EpollDomainSocketChannel(Channel parent, FileDescriptor fd) { + super(parent, new LinuxSocket(fd.intValue())); } - /** - * @deprecated Use {@link #EpollDomainSocketChannel(Socket, boolean)}. - *

- * Creates a new {@link EpollDomainSocketChannel} from an existing {@link FileDescriptor} - */ - @Deprecated - public EpollDomainSocketChannel(FileDescriptor fd) { + public EpollDomainSocketChannel(int fd) { super(fd); } - public EpollDomainSocketChannel(Channel parent, Socket fd) { + public EpollDomainSocketChannel(Channel parent, LinuxSocket fd) { super(parent, fd); } - /** - * Creates a new {@link EpollDomainSocketChannel} from an existing {@link FileDescriptor} - */ - public EpollDomainSocketChannel(Socket fd, boolean active) { - super(fd, active); + public EpollDomainSocketChannel(int fd, boolean active) { + super(new LinuxSocket(fd), active); } @Override @@ -86,7 +73,7 @@ public final class EpollDomainSocketChannel extends AbstractEpollStreamChannel i @Override protected void doBind(SocketAddress localAddress) throws Exception { - fd().bind(localAddress); + socket.bind(localAddress); local = (DomainSocketAddress) localAddress; } @@ -118,7 +105,7 @@ public final class EpollDomainSocketChannel extends AbstractEpollStreamChannel i @Override protected boolean doWriteSingle(ChannelOutboundBuffer in, int writeSpinCount) throws Exception { Object msg = in.current(); - if (msg instanceof FileDescriptor && Native.sendFd(fd().intValue(), ((FileDescriptor) msg).intValue()) > 0) { + if (msg instanceof FileDescriptor && socket.sendFd(((FileDescriptor) msg).intValue()) > 0) { // File descriptor was written, so remove it. in.remove(); return true; @@ -138,8 +125,9 @@ public final class EpollDomainSocketChannel extends AbstractEpollStreamChannel i * Returns the unix credentials (uid, gid, pid) of the peer * SO_PEERCRED */ + @UnstableApi public PeerCredentials peerCredentials() throws IOException { - return fd().getPeerCredentials(); + return socket.getPeerCredentials(); } private final class EpollDomainUnsafe extends EpollStreamUnsafe { @@ -158,7 +146,7 @@ public final class EpollDomainSocketChannel extends AbstractEpollStreamChannel i } private void epollInReadFd() { - if (fd().isInputShutdown()) { + if (socket.isInputShutdown()) { clearEpollIn0(); return; } @@ -175,7 +163,7 @@ public final class EpollDomainSocketChannel extends AbstractEpollStreamChannel i // lastBytesRead represents the fd. We use lastBytesRead because it must be set so that the // EpollRecvByteAllocatorHandle knows if it should try to read again or not when autoRead is // enabled. - allocHandle.lastBytesRead(Native.recvFd(fd().intValue())); + allocHandle.lastBytesRead(socket.recvFd()); switch(allocHandle.lastBytesRead()) { case 0: break readLoop; diff --git a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollEventLoop.java b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollEventLoop.java index 52abf7a59b..6b43c229a9 100644 --- a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollEventLoop.java +++ b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollEventLoop.java @@ -21,6 +21,7 @@ import io.netty.channel.SelectStrategy; import io.netty.channel.SingleThreadEventLoop; import io.netty.channel.epoll.AbstractEpollChannel.AbstractEpollUnsafe; import io.netty.channel.unix.FileDescriptor; +import io.netty.channel.unix.IovArray; import io.netty.util.IntSupplier; import io.netty.util.collection.IntObjectHashMap; import io.netty.util.collection.IntObjectMap; @@ -46,6 +47,12 @@ final class EpollEventLoop extends SingleThreadEventLoop { private static final AtomicIntegerFieldUpdater WAKEN_UP_UPDATER = AtomicIntegerFieldUpdater.newUpdater(EpollEventLoop.class, "wakenUp"); + static { + // Ensure JNI is initialized by the time this class is loaded by this time! + // We use unix-common methods in this class which are backed by JNI methods. + Epoll.ensureAvailability(); + } + private final FileDescriptor epollFd; private final FileDescriptor eventFd; private final IntObjectMap channels = new IntObjectHashMap(4096); @@ -132,7 +139,7 @@ final class EpollEventLoop extends SingleThreadEventLoop { */ void add(AbstractEpollChannel ch) throws IOException { assert inEventLoop(); - int fd = ch.fd().intValue(); + int fd = ch.socket.intValue(); Native.epollCtlAdd(epollFd.intValue(), fd, ch.flags); channels.put(fd, ch); } @@ -142,7 +149,7 @@ final class EpollEventLoop extends SingleThreadEventLoop { */ void modify(AbstractEpollChannel ch) throws IOException { assert inEventLoop(); - Native.epollCtlMod(epollFd.intValue(), ch.fd().intValue(), ch.flags); + Native.epollCtlMod(epollFd.intValue(), ch.socket.intValue(), ch.flags); } /** @@ -152,7 +159,7 @@ final class EpollEventLoop extends SingleThreadEventLoop { assert inEventLoop(); if (ch.isOpen()) { - int fd = ch.fd().intValue(); + int fd = ch.socket.intValue(); if (channels.remove(fd) != null) { // Remove the epoll. This is only needed if it's still open as otherwise it will be automatically // removed once the file-descriptor is closed. diff --git a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollEventLoopGroup.java b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollEventLoopGroup.java index fe857c43f1..f62bf88849 100644 --- a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollEventLoopGroup.java +++ b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollEventLoopGroup.java @@ -33,6 +33,10 @@ import java.util.concurrent.ThreadFactory; * it only works on linux. */ public final class EpollEventLoopGroup extends MultithreadEventLoopGroup { + static { + // Ensure JNI is initialized by the time this class is loaded by this time! + Epoll.ensureAvailability(); + } /** * Create a new instance using the default number of threads and the default {@link ThreadFactory}. diff --git a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollRecvByteAllocatorHandle.java b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollRecvByteAllocatorHandle.java index 658cd3c6fb..5c2c87c0c5 100644 --- a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollRecvByteAllocatorHandle.java +++ b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollRecvByteAllocatorHandle.java @@ -24,14 +24,14 @@ import io.netty.util.internal.ObjectUtil; class EpollRecvByteAllocatorHandle implements RecvByteBufAllocator.ExtendedHandle { private final RecvByteBufAllocator.ExtendedHandle delegate; - private boolean isEdgeTriggered; - private boolean receivedRdHup; private final UncheckedBooleanSupplier defaultMaybeMoreDataSupplier = new UncheckedBooleanSupplier() { @Override public boolean get() { return maybeMoreDataToRead(); } }; + private boolean isEdgeTriggered; + private boolean receivedRdHup; EpollRecvByteAllocatorHandle(RecvByteBufAllocator.ExtendedHandle handle) { this.delegate = ObjectUtil.checkNotNull(handle, "handle"); diff --git a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollServerChannelConfig.java b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollServerChannelConfig.java index f7eb74eca6..5d6394b142 100644 --- a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollServerChannelConfig.java +++ b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollServerChannelConfig.java @@ -21,6 +21,7 @@ import io.netty.channel.ChannelOption; import io.netty.channel.MessageSizeEstimator; import io.netty.channel.RecvByteBufAllocator; import io.netty.channel.WriteBufferWaterMark; +import io.netty.channel.socket.ServerSocketChannelConfig; import io.netty.util.NetUtil; import java.io.IOException; @@ -30,7 +31,7 @@ import static io.netty.channel.ChannelOption.SO_BACKLOG; import static io.netty.channel.ChannelOption.SO_RCVBUF; import static io.netty.channel.ChannelOption.SO_REUSEADDR; -public class EpollServerChannelConfig extends EpollChannelConfig { +public class EpollServerChannelConfig extends EpollChannelConfig implements ServerSocketChannelConfig { protected final AbstractEpollChannel channel; private volatile int backlog = NetUtil.SOMAXCONN; private volatile int pendingFastOpenRequestsThreshold; @@ -84,7 +85,7 @@ public class EpollServerChannelConfig extends EpollChannelConfig { public boolean isReuseAddress() { try { - return Native.isReuseAddress(channel.fd().intValue()) == 1; + return channel.socket.isReuseAddress(); } catch (IOException e) { throw new ChannelException(e); } @@ -92,7 +93,7 @@ public class EpollServerChannelConfig extends EpollChannelConfig { public EpollServerChannelConfig setReuseAddress(boolean reuseAddress) { try { - Native.setReuseAddress(channel.fd().intValue(), reuseAddress ? 1 : 0); + channel.socket.setReuseAddress(reuseAddress); return this; } catch (IOException e) { throw new ChannelException(e); @@ -101,7 +102,7 @@ public class EpollServerChannelConfig extends EpollChannelConfig { public int getReceiveBufferSize() { try { - return channel.fd().getReceiveBufferSize(); + return channel.socket.getReceiveBufferSize(); } catch (IOException e) { throw new ChannelException(e); } @@ -109,7 +110,7 @@ public class EpollServerChannelConfig extends EpollChannelConfig { public EpollServerChannelConfig setReceiveBufferSize(int receiveBufferSize) { try { - channel.fd().setReceiveBufferSize(receiveBufferSize); + channel.socket.setReceiveBufferSize(receiveBufferSize); return this; } catch (IOException e) { throw new ChannelException(e); @@ -154,6 +155,11 @@ public class EpollServerChannelConfig extends EpollChannelConfig { return this; } + @Override + public EpollServerChannelConfig setPerformancePreferences(int connectionTime, int latency, int bandwidth) { + return this; + } + @Override public EpollServerChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis) { super.setConnectTimeoutMillis(connectTimeoutMillis); diff --git a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollServerDomainSocketChannel.java b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollServerDomainSocketChannel.java index c533d24ea1..9ec13ef1e0 100644 --- a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollServerDomainSocketChannel.java +++ b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollServerDomainSocketChannel.java @@ -17,7 +17,6 @@ package io.netty.channel.epoll; import io.netty.channel.Channel; import io.netty.channel.unix.DomainSocketAddress; -import io.netty.channel.unix.FileDescriptor; import io.netty.channel.unix.ServerDomainSocketChannel; import io.netty.channel.unix.Socket; import io.netty.util.internal.logging.InternalLogger; @@ -26,8 +25,7 @@ import io.netty.util.internal.logging.InternalLoggerFactory; import java.io.File; import java.net.SocketAddress; -import static io.netty.channel.unix.Socket.newSocketDomain; - +import static io.netty.channel.epoll.LinuxSocket.newSocketDomain; public final class EpollServerDomainSocketChannel extends AbstractEpollServerChannel implements ServerDomainSocketChannel { @@ -41,22 +39,15 @@ public final class EpollServerDomainSocketChannel extends AbstractEpollServerCha super(newSocketDomain(), false); } - /** - * @deprecated Use {@link #EpollServerDomainSocketChannel(Socket, boolean)}. - * Creates a new {@link EpollServerDomainSocketChannel} from an existing {@link FileDescriptor}. - */ - public EpollServerDomainSocketChannel(FileDescriptor fd) { + public EpollServerDomainSocketChannel(int fd) { super(fd); } - /** - * @deprecated Use {@link #EpollServerDomainSocketChannel(Socket, boolean)}. - */ - public EpollServerDomainSocketChannel(Socket fd) { + EpollServerDomainSocketChannel(LinuxSocket fd) { super(fd); } - public EpollServerDomainSocketChannel(Socket fd, boolean active) { + EpollServerDomainSocketChannel(LinuxSocket fd, boolean active) { super(fd, active); } @@ -72,8 +63,8 @@ public final class EpollServerDomainSocketChannel extends AbstractEpollServerCha @Override protected void doBind(SocketAddress localAddress) throws Exception { - fd().bind(localAddress); - fd().listen(config.getBacklog()); + socket.bind(localAddress); + socket.listen(config.getBacklog()); local = (DomainSocketAddress) localAddress; active = true; } diff --git a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollServerSocketChannel.java b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollServerSocketChannel.java index 70cd3e871c..f0f87919b1 100644 --- a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollServerSocketChannel.java +++ b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollServerSocketChannel.java @@ -18,8 +18,6 @@ package io.netty.channel.epoll; import io.netty.channel.Channel; import io.netty.channel.EventLoop; import io.netty.channel.socket.ServerSocketChannel; -import io.netty.channel.unix.FileDescriptor; -import io.netty.channel.unix.Socket; import java.io.IOException; import java.net.InetAddress; @@ -29,8 +27,8 @@ import java.util.Collection; import java.util.Collections; import java.util.Map; +import static io.netty.channel.epoll.LinuxSocket.newSocketStream; import static io.netty.channel.unix.NativeInetAddress.address; -import static io.netty.channel.unix.Socket.newSocketStream; /** * {@link ServerSocketChannel} implementation that uses linux EPOLL Edge-Triggered Mode for @@ -47,23 +45,13 @@ public final class EpollServerSocketChannel extends AbstractEpollServerChannel i config = new EpollServerSocketChannelConfig(this); } - /** - * @deprecated Use {@link #EpollServerSocketChannel(Socket, boolean)}. - * Creates a new {@link EpollServerSocketChannel} from an existing {@link FileDescriptor}. - */ - @Deprecated - public EpollServerSocketChannel(FileDescriptor fd) { + public EpollServerSocketChannel(int fd) { // Must call this constructor to ensure this object's local address is configured correctly. // The local address can only be obtained from a Socket object. - this(new Socket(fd.intValue())); + this(new LinuxSocket(fd)); } - /** - * @deprecated Use {@link #EpollServerSocketChannel(Socket, boolean)}. - * Creates a new {@link EpollServerSocketChannel} from an existing {@link Socket}. - */ - @Deprecated - public EpollServerSocketChannel(Socket fd) { + EpollServerSocketChannel(LinuxSocket fd) { super(fd); // As we create an EpollServerSocketChannel from a FileDescriptor we should try to obtain the remote and local // address from it. This is needed as the FileDescriptor may be bound already. @@ -71,7 +59,7 @@ public final class EpollServerSocketChannel extends AbstractEpollServerChannel i config = new EpollServerSocketChannelConfig(this); } - public EpollServerSocketChannel(Socket fd, boolean active) { + EpollServerSocketChannel(LinuxSocket fd, boolean active) { super(fd, active); // As we create an EpollServerSocketChannel from a FileDescriptor we should try to obtain the remote and local // address from it. This is needed as the FileDescriptor may be bound already. @@ -88,12 +76,12 @@ public final class EpollServerSocketChannel extends AbstractEpollServerChannel i protected void doBind(SocketAddress localAddress) throws Exception { InetSocketAddress addr = (InetSocketAddress) localAddress; checkResolvable(addr); - fd().bind(addr); - local = fd().localAddress(); + socket.bind(addr); + local = socket.localAddress(); if (Native.IS_SUPPORTING_TCP_FASTOPEN && config.getTcpFastopen() > 0) { - Native.setTcpFastopen(fd().intValue(), config.getTcpFastopen()); + socket.setTcpFastOpen(config.getTcpFastopen()); } - fd().listen(config.getBacklog()); + socket.listen(config.getBacklog()); active = true; } @@ -119,7 +107,7 @@ public final class EpollServerSocketChannel extends AbstractEpollServerChannel i @Override protected Channel newChildChannel(int fd, byte[] address, int offset, int len) throws Exception { - return new EpollSocketChannel(this, new Socket(fd), address(address, offset, len)); + return new EpollSocketChannel(this, new LinuxSocket(fd), address(address, offset, len)); } Collection tcpMd5SigAddresses() { diff --git a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollServerSocketChannelConfig.java b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollServerSocketChannelConfig.java index dabcaf02d6..7d6727f84b 100644 --- a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollServerSocketChannelConfig.java +++ b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollServerSocketChannelConfig.java @@ -186,7 +186,7 @@ public final class EpollServerSocketChannelConfig extends EpollServerChannelConf */ public boolean isReusePort() { try { - return Native.isReusePort(channel.fd().intValue()) == 1; + return channel.socket.isReusePort(); } catch (IOException e) { throw new ChannelException(e); } @@ -201,7 +201,7 @@ public final class EpollServerSocketChannelConfig extends EpollServerChannelConf */ public EpollServerSocketChannelConfig setReusePort(boolean reusePort) { try { - Native.setReusePort(channel.fd().intValue(), reusePort ? 1 : 0); + channel.socket.setReusePort(reusePort); return this; } catch (IOException e) { throw new ChannelException(e); @@ -214,7 +214,7 @@ public final class EpollServerSocketChannelConfig extends EpollServerChannelConf */ public boolean isFreeBind() { try { - return Native.isIpFreeBind(channel.fd().intValue()) != 0; + return channel.socket.isIpFreeBind(); } catch (IOException e) { throw new ChannelException(e); } @@ -226,7 +226,7 @@ public final class EpollServerSocketChannelConfig extends EpollServerChannelConf */ public EpollServerSocketChannelConfig setFreeBind(boolean freeBind) { try { - Native.setIpFreeBind(channel.fd().intValue(), freeBind ? 1 : 0); + channel.socket.setIpFreeBind(freeBind); return this; } catch (IOException e) { throw new ChannelException(e); @@ -238,7 +238,7 @@ public final class EpollServerSocketChannelConfig extends EpollServerChannelConf */ public EpollServerSocketChannelConfig setTcpDeferAccept(int deferAccept) { try { - channel.fd().setTcpDeferAccept(deferAccept); + channel.socket.setTcpDeferAccept(deferAccept); return this; } catch (IOException e) { throw new ChannelException(e); @@ -250,7 +250,7 @@ public final class EpollServerSocketChannelConfig extends EpollServerChannelConf */ public int getTcpDeferAccept() { try { - return channel.fd().getTcpDeferAccept(); + return channel.socket.getTcpDeferAccept(); } catch (IOException e) { throw new ChannelException(e); } diff --git a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollSocketChannel.java b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollSocketChannel.java index 18bd00b33b..0d7befff6b 100644 --- a/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollSocketChannel.java +++ b/transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollSocketChannel.java @@ -19,8 +19,6 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelException; import io.netty.channel.socket.ServerSocketChannel; import io.netty.channel.socket.SocketChannel; -import io.netty.channel.unix.FileDescriptor; -import io.netty.channel.unix.Socket; import io.netty.util.concurrent.GlobalEventExecutor; import io.netty.util.internal.PlatformDependent; @@ -35,7 +33,7 @@ import java.util.Collections; import java.util.Map; import java.util.concurrent.Executor; -import static io.netty.channel.unix.Socket.newSocketStream; +import static io.netty.channel.epoll.LinuxSocket.newSocketStream; /** * {@link SocketChannel} implementation that uses linux EPOLL Edge-Triggered Mode for @@ -51,7 +49,7 @@ public final class EpollSocketChannel extends AbstractEpollStreamChannel impleme private volatile Collection tcpMd5SigAddresses = Collections.emptyList(); - EpollSocketChannel(Channel parent, Socket fd, InetSocketAddress remote) { + EpollSocketChannel(Channel parent, LinuxSocket fd, InetSocketAddress remote) { super(parent, fd); config = new EpollSocketChannelConfig(this); // Directly cache the remote and local addresses @@ -69,23 +67,16 @@ public final class EpollSocketChannel extends AbstractEpollStreamChannel impleme config = new EpollSocketChannelConfig(this); } - /** - * @deprecated Use {@link #EpollSocketChannel(Socket, boolean)}. - */ - @Deprecated - public EpollSocketChannel(FileDescriptor fd) { + public EpollSocketChannel(int fd) { super(fd); // As we create an EpollSocketChannel from a FileDescriptor we should try to obtain the remote and local // address from it. This is needed as the FileDescriptor may be bound/connected already. - remote = fd().remoteAddress(); - local = fd().localAddress(); + remote = socket.remoteAddress(); + local = socket.localAddress(); config = new EpollSocketChannelConfig(this); } - /** - * Creates a new {@link EpollSocketChannel} from an existing {@link FileDescriptor}. - */ - public EpollSocketChannel(Socket fd, boolean active) { + EpollSocketChannel(LinuxSocket fd, boolean active) { super(fd, active); // As we create an EpollSocketChannel from a FileDescriptor we should try to obtain the remote and local // address from it. This is needed as the FileDescriptor may be bound/connected already. @@ -107,7 +98,7 @@ public final class EpollSocketChannel extends AbstractEpollStreamChannel impleme */ public EpollTcpInfo tcpInfo(EpollTcpInfo info) { try { - Native.tcpInfo(fd().intValue(), info); + socket.getTcpInfo(info); return info; } catch (IOException e) { throw new ChannelException(e); @@ -137,8 +128,8 @@ public final class EpollSocketChannel extends AbstractEpollStreamChannel impleme @Override protected void doBind(SocketAddress local) throws Exception { InetSocketAddress localAddress = (InetSocketAddress) local; - fd().bind(localAddress); - this.local = fd().localAddress(); + socket.bind(localAddress); + this.local = socket.localAddress(); } @Override @@ -192,7 +183,7 @@ public final class EpollSocketChannel extends AbstractEpollStreamChannel impleme boolean connected = super.doConnect(remoteAddress, localAddress); if (connected) { - remote = computeRemoteAddr(remoteAddr, fd().remoteAddress()); + remote = computeRemoteAddr(remoteAddr, socket.remoteAddress()); } else { // Store for later usage in doFinishConnect() requestedRemote = remoteAddr; @@ -200,7 +191,7 @@ public final class EpollSocketChannel extends AbstractEpollStreamChannel impleme // We always need to set the localAddress even if not connected yet as the bind already took place. // // See https://github.com/netty/netty/issues/3463 - local = fd().localAddress(); + local = socket.localAddress(); return connected; } @@ -229,7 +220,7 @@ public final class EpollSocketChannel extends AbstractEpollStreamChannel impleme @Override boolean doFinishConnect() throws Exception { if (super.doFinishConnect()) { - remote = computeRemoteAddr(requestedRemote, fd().remoteAddress()); + remote = computeRemoteAddr(requestedRemote, socket.remoteAddress()); requestedRemote = null; return true; } 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 79052062a5..4ac335619b 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 @@ -28,10 +28,16 @@ import java.io.IOException; import java.net.InetAddress; import java.util.Map; -import static io.netty.channel.ChannelOption.*; +import static io.netty.channel.ChannelOption.ALLOW_HALF_CLOSURE; +import static io.netty.channel.ChannelOption.IP_TOS; +import static io.netty.channel.ChannelOption.SO_KEEPALIVE; +import static io.netty.channel.ChannelOption.SO_LINGER; +import static io.netty.channel.ChannelOption.SO_RCVBUF; +import static io.netty.channel.ChannelOption.SO_REUSEADDR; +import static io.netty.channel.ChannelOption.SO_SNDBUF; +import static io.netty.channel.ChannelOption.TCP_NODELAY; public final class EpollSocketChannelConfig extends EpollChannelConfig implements SocketChannelConfig { - private static final long MAX_UINT32_T = 0xFFFFFFFFL; private final EpollSocketChannel channel; private volatile boolean allowHalfClosure; @@ -156,7 +162,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement @Override public int getReceiveBufferSize() { try { - return channel.fd().getReceiveBufferSize(); + return channel.socket.getReceiveBufferSize(); } catch (IOException e) { throw new ChannelException(e); } @@ -165,7 +171,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement @Override public int getSendBufferSize() { try { - return channel.fd().getSendBufferSize(); + return channel.socket.getSendBufferSize(); } catch (IOException e) { throw new ChannelException(e); } @@ -174,7 +180,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement @Override public int getSoLinger() { try { - return channel.fd().getSoLinger(); + return channel.socket.getSoLinger(); } catch (IOException e) { throw new ChannelException(e); } @@ -183,7 +189,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement @Override public int getTrafficClass() { try { - return Native.getTrafficClass(channel.fd().intValue()); + return channel.socket.getTrafficClass(); } catch (IOException e) { throw new ChannelException(e); } @@ -192,7 +198,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement @Override public boolean isKeepAlive() { try { - return channel.fd().isKeepAlive(); + return channel.socket.isKeepAlive(); } catch (IOException e) { throw new ChannelException(e); } @@ -201,7 +207,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement @Override public boolean isReuseAddress() { try { - return Native.isReuseAddress(channel.fd().intValue()) == 1; + return channel.socket.isReuseAddress(); } catch (IOException e) { throw new ChannelException(e); } @@ -210,7 +216,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement @Override public boolean isTcpNoDelay() { try { - return channel.fd().isTcpNoDelay(); + return channel.socket.isTcpNoDelay(); } catch (IOException e) { throw new ChannelException(e); } @@ -221,7 +227,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement */ public boolean isTcpCork() { try { - return channel.fd().isTcpCork(); + return channel.socket.isTcpCork(); } catch (IOException e) { throw new ChannelException(e); } @@ -233,7 +239,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement */ public long getTcpNotSentLowAt() { try { - return Native.getTcpNotSentLowAt(channel.fd().intValue()) & MAX_UINT32_T; + return channel.socket.getTcpNotSentLowAt(); } catch (IOException e) { throw new ChannelException(e); } @@ -244,7 +250,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement */ public int getTcpKeepIdle() { try { - return Native.getTcpKeepIdle(channel.fd().intValue()); + return channel.socket.getTcpKeepIdle(); } catch (IOException e) { throw new ChannelException(e); } @@ -255,7 +261,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement */ public int getTcpKeepIntvl() { try { - return Native.getTcpKeepIntvl(channel.fd().intValue()); + return channel.socket.getTcpKeepIntvl(); } catch (IOException e) { throw new ChannelException(e); } @@ -266,7 +272,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement */ public int getTcpKeepCnt() { try { - return Native.getTcpKeepCnt(channel.fd().intValue()); + return channel.socket.getTcpKeepCnt(); } catch (IOException e) { throw new ChannelException(e); } @@ -277,7 +283,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement */ public int getTcpUserTimeout() { try { - return Native.getTcpUserTimeout(channel.fd().intValue()); + return channel.socket.getTcpUserTimeout(); } catch (IOException e) { throw new ChannelException(e); } @@ -286,7 +292,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement @Override public EpollSocketChannelConfig setKeepAlive(boolean keepAlive) { try { - channel.fd().setKeepAlive(keepAlive); + channel.socket.setKeepAlive(keepAlive); return this; } catch (IOException e) { throw new ChannelException(e); @@ -302,7 +308,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement @Override public EpollSocketChannelConfig setReceiveBufferSize(int receiveBufferSize) { try { - channel.fd().setReceiveBufferSize(receiveBufferSize); + channel.socket.setReceiveBufferSize(receiveBufferSize); return this; } catch (IOException e) { throw new ChannelException(e); @@ -312,7 +318,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement @Override public EpollSocketChannelConfig setReuseAddress(boolean reuseAddress) { try { - Native.setReuseAddress(channel.fd().intValue(), reuseAddress ? 1 : 0); + channel.socket.setReuseAddress(reuseAddress); return this; } catch (IOException e) { throw new ChannelException(e); @@ -322,7 +328,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement @Override public EpollSocketChannelConfig setSendBufferSize(int sendBufferSize) { try { - channel.fd().setSendBufferSize(sendBufferSize); + channel.socket.setSendBufferSize(sendBufferSize); return this; } catch (IOException e) { throw new ChannelException(e); @@ -332,7 +338,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement @Override public EpollSocketChannelConfig setSoLinger(int soLinger) { try { - channel.fd().setSoLinger(soLinger); + channel.socket.setSoLinger(soLinger); return this; } catch (IOException e) { throw new ChannelException(e); @@ -342,7 +348,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement @Override public EpollSocketChannelConfig setTcpNoDelay(boolean tcpNoDelay) { try { - channel.fd().setTcpNoDelay(tcpNoDelay); + channel.socket.setTcpNoDelay(tcpNoDelay); return this; } catch (IOException e) { throw new ChannelException(e); @@ -354,7 +360,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement */ public EpollSocketChannelConfig setTcpCork(boolean tcpCork) { try { - channel.fd().setTcpCork(tcpCork); + channel.socket.setTcpCork(tcpCork); return this; } catch (IOException e) { throw new ChannelException(e); @@ -366,11 +372,8 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement * @param tcpNotSentLowAt is a uint32_t */ public EpollSocketChannelConfig setTcpNotSentLowAt(long tcpNotSentLowAt) { - if (tcpNotSentLowAt < 0 || tcpNotSentLowAt > MAX_UINT32_T) { - throw new IllegalArgumentException("tcpNotSentLowAt must be a uint32_t"); - } try { - Native.setTcpNotSentLowAt(channel.fd().intValue(), (int) tcpNotSentLowAt); + channel.socket.setTcpNotSentLowAt(tcpNotSentLowAt); return this; } catch (IOException e) { throw new ChannelException(e); @@ -380,7 +383,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement @Override public EpollSocketChannelConfig setTrafficClass(int trafficClass) { try { - Native.setTrafficClass(channel.fd().intValue(), trafficClass); + channel.socket.setTrafficClass(trafficClass); return this; } catch (IOException e) { throw new ChannelException(e); @@ -392,7 +395,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement */ public EpollSocketChannelConfig setTcpKeepIdle(int seconds) { try { - Native.setTcpKeepIdle(channel.fd().intValue(), seconds); + channel.socket.setTcpKeepIdle(seconds); return this; } catch (IOException e) { throw new ChannelException(e); @@ -404,7 +407,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement */ public EpollSocketChannelConfig setTcpKeepIntvl(int seconds) { try { - Native.setTcpKeepIntvl(channel.fd().intValue(), seconds); + channel.socket.setTcpKeepIntvl(seconds); return this; } catch (IOException e) { throw new ChannelException(e); @@ -416,7 +419,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement */ public EpollSocketChannelConfig setTcpKeepCntl(int probes) { try { - Native.setTcpKeepCnt(channel.fd().intValue(), probes); + channel.socket.setTcpKeepCnt(probes); return this; } catch (IOException e) { throw new ChannelException(e); @@ -428,7 +431,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement */ public EpollSocketChannelConfig setTcpUserTimeout(int milliseconds) { try { - Native.setTcpUserTimeout(channel.fd().intValue(), milliseconds); + channel.socket.setTcpUserTimeout(milliseconds); return this; } catch (IOException e) { throw new ChannelException(e); @@ -455,7 +458,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement */ public EpollSocketChannelConfig setTcpQuickAck(boolean quickAck) { try { - channel.fd().setTcpQuickAck(quickAck); + channel.socket.setTcpQuickAck(quickAck); return this; } catch (IOException e) { throw new ChannelException(e); @@ -468,7 +471,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement */ public boolean isTcpQuickAck() { try { - return channel.fd().isTcpQuickAck(); + return channel.socket.isTcpQuickAck(); } catch (IOException e) { throw new ChannelException(e); } diff --git a/transport-native-epoll/src/main/java/io/netty/channel/epoll/LinuxSocket.java b/transport-native-epoll/src/main/java/io/netty/channel/epoll/LinuxSocket.java new file mode 100644 index 0000000000..72b3cbd776 --- /dev/null +++ b/transport-native-epoll/src/main/java/io/netty/channel/epoll/LinuxSocket.java @@ -0,0 +1,162 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.epoll; + +import io.netty.channel.unix.NativeInetAddress; +import io.netty.channel.unix.PeerCredentials; +import io.netty.channel.unix.Socket; + +import java.io.IOException; +import java.net.InetAddress; + +/** + * A socket which provides access Linux native methods. + */ +final class LinuxSocket extends Socket { + private static final long MAX_UINT32_T = 0xFFFFFFFFL; + + public LinuxSocket(int fd) { + super(fd); + } + + void setTcpDeferAccept(int deferAccept) throws IOException { + setTcpDeferAccept(intValue(), deferAccept); + } + + void setTcpQuickAck(boolean quickAck) throws IOException { + setTcpQuickAck(intValue(), quickAck ? 1 : 0); + } + + void setTcpCork(boolean tcpCork) throws IOException { + setTcpCork(intValue(), tcpCork ? 1 : 0); + } + + void setTcpNotSentLowAt(long tcpNotSentLowAt) throws IOException { + if (tcpNotSentLowAt < 0 || tcpNotSentLowAt > MAX_UINT32_T) { + throw new IllegalArgumentException("tcpNotSentLowAt must be a uint32_t"); + } + setTcpNotSentLowAt(intValue(), (int) tcpNotSentLowAt); + } + + void setTcpFastOpen(int tcpFastopenBacklog) throws IOException { + setTcpFastOpen(intValue(), tcpFastopenBacklog); + } + + void setTcpKeepIdle(int seconds) throws IOException { + setTcpKeepIdle(intValue(), seconds); + } + + void setTcpKeepIntvl(int seconds) throws IOException { + setTcpKeepIntvl(intValue(), seconds); + } + + void setTcpKeepCnt(int probes) throws IOException { + setTcpKeepCnt(intValue(), probes); + } + + void setTcpUserTimeout(int milliseconds) throws IOException { + setTcpUserTimeout(intValue(), milliseconds); + } + + void setIpFreeBind(boolean enabled) throws IOException { + setIpFreeBind(intValue(), enabled ? 1 : 0); + } + + void getTcpInfo(EpollTcpInfo info) throws IOException { + getTcpInfo(intValue(), info.info); + } + + void setTcpMd5Sig(InetAddress address, byte[] key) throws IOException { + final NativeInetAddress a = NativeInetAddress.newInstance(address); + setTcpMd5Sig(intValue(), a.address(), a.scopeId(), key); + } + + boolean isTcpCork() throws IOException { + return isTcpCork(intValue()) != 0; + } + + int getTcpDeferAccept() throws IOException { + return getTcpDeferAccept(intValue()); + } + + boolean isTcpQuickAck() throws IOException { + return isTcpQuickAck(intValue()) != 0; + } + + long getTcpNotSentLowAt() throws IOException { + return getTcpNotSentLowAt(intValue()) & MAX_UINT32_T; + } + + int getTcpKeepIdle() throws IOException { + return getTcpKeepIdle(intValue()); + } + + int getTcpKeepIntvl() throws IOException { + return getTcpKeepIntvl(intValue()); + } + + int getTcpKeepCnt() throws IOException { + return getTcpKeepCnt(intValue()); + } + + int getTcpUserTimeout() throws IOException { + return getTcpUserTimeout(intValue()); + } + + boolean isIpFreeBind() throws IOException { + return isIpFreeBind(intValue()) != 0; + } + + PeerCredentials getPeerCredentials() throws IOException { + return getPeerCredentials(intValue()); + } + + public static LinuxSocket newSocketStream() { + return new LinuxSocket(newSocketStream0()); + } + + public static LinuxSocket newSocketDgram() { + return new LinuxSocket(newSocketDgram0()); + } + + public static LinuxSocket newSocketDomain() { + return new LinuxSocket(newSocketDomain0()); + } + + private static native int getTcpDeferAccept(int fd) throws IOException; + private static native int isTcpQuickAck(int fd) throws IOException; + private static native int isTcpCork(int fd) throws IOException; + private static native int getTcpNotSentLowAt(int fd) throws IOException; + private static native int getTcpKeepIdle(int fd) throws IOException; + private static native int getTcpKeepIntvl(int fd) throws IOException; + private static native int getTcpKeepCnt(int fd) throws IOException; + private static native int getTcpUserTimeout(int fd) throws IOException; + private static native int isIpFreeBind(int fd) throws IOException; + private static native void getTcpInfo(int fd, int[] array) throws IOException; + private static native PeerCredentials getPeerCredentials(int fd) throws IOException; + + private static native void setTcpDeferAccept(int fd, int deferAccept) throws IOException; + private static native void setTcpQuickAck(int fd, int quickAck) throws IOException; + private static native void setTcpCork(int fd, int tcpCork) throws IOException; + private static native void setTcpNotSentLowAt(int fd, int tcpNotSentLowAt) throws IOException; + private static native void setTcpFastOpen(int fd, int tcpFastopenBacklog) throws IOException; + private static native void setTcpKeepIdle(int fd, int seconds) throws IOException; + private static native void setTcpKeepIntvl(int fd, int seconds) throws IOException; + private static native void setTcpKeepCnt(int fd, int probes) throws IOException; + private static native void setTcpUserTimeout(int fd, int milliseconds)throws IOException; + private static native void setIpFreeBind(int fd, int freeBind) throws IOException; + private static native void setTcpMd5Sig(int fd, byte[] address, int scopeId, byte[] key) throws IOException; +} 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 215ffaa165..30557d01b7 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 @@ -34,13 +34,10 @@ import static io.netty.channel.epoll.NativeStaticallyReferencedJniMethods.epolle import static io.netty.channel.epoll.NativeStaticallyReferencedJniMethods.epollin; import static io.netty.channel.epoll.NativeStaticallyReferencedJniMethods.epollout; import static io.netty.channel.epoll.NativeStaticallyReferencedJniMethods.epollrdhup; -import static io.netty.channel.epoll.NativeStaticallyReferencedJniMethods.iovMax; import static io.netty.channel.epoll.NativeStaticallyReferencedJniMethods.isSupportingSendmmsg; import static io.netty.channel.epoll.NativeStaticallyReferencedJniMethods.isSupportingTcpFastopen; import static io.netty.channel.epoll.NativeStaticallyReferencedJniMethods.kernelVersion; -import static io.netty.channel.epoll.NativeStaticallyReferencedJniMethods.ssizeMax; import static io.netty.channel.epoll.NativeStaticallyReferencedJniMethods.tcpMd5SigMaxKeyLen; -import static io.netty.channel.epoll.NativeStaticallyReferencedJniMethods.uioMaxIov; import static io.netty.channel.unix.Errors.ERRNO_EAGAIN_NEGATIVE; import static io.netty.channel.unix.Errors.ERRNO_EPIPE_NEGATIVE; import static io.netty.channel.unix.Errors.ERRNO_EWOULDBLOCK_NEGATIVE; @@ -72,11 +69,8 @@ public final class Native { public static final int EPOLLET = epollet(); public static final int EPOLLERR = epollerr(); - public static final int IOV_MAX = iovMax(); - public static final int UIO_MAX_IOV = uioMaxIov(); public static final boolean IS_SUPPORTING_SENDMMSG = isSupportingSendmmsg(); public static final boolean IS_SUPPORTING_TCP_FASTOPEN = isSupportingTcpFastopen(); - public static final long SSIZE_MAX = ssizeMax(); public static final int TCP_MD5SIG_MAXKEYLEN = tcpMd5SigMaxKeyLen(); public static final String KERNEL_VERSION = kernelVersion(); @@ -185,82 +179,10 @@ public final class Native { private static native int sendmmsg0( int fd, NativeDatagramPacketArray.NativeDatagramPacket[] msgs, int offset, int len); - public static int recvFd(int fd) throws IOException { - int res = recvFd0(fd); - if (res > 0) { - return res; - } - if (res == 0) { - return -1; - } - - if (res == ERRNO_EAGAIN_NEGATIVE || res == ERRNO_EWOULDBLOCK_NEGATIVE) { - // Everything consumed so just return -1 here. - return 0; - } - throw newIOException("recvFd", res); - } - - private static native int recvFd0(int fd); - - public static int sendFd(int socketFd, int fd) throws IOException { - int res = sendFd0(socketFd, fd); - if (res >= 0) { - return res; - } - if (res == ERRNO_EAGAIN_NEGATIVE || res == ERRNO_EWOULDBLOCK_NEGATIVE) { - // Everything consumed so just return -1 here. - return -1; - } - throw newIOException("sendFd", res); - } - - private static native int sendFd0(int socketFd, int fd); - - // Socket option operations - public static native int isReuseAddress(int fd) throws IOException; - public static native int isReusePort(int fd) throws IOException; - public static native int getTcpNotSentLowAt(int fd) throws IOException; - public static native int getTrafficClass(int fd) throws IOException; - public static native int isBroadcast(int fd) throws IOException; - public static native int getTcpKeepIdle(int fd) throws IOException; - public static native int getTcpKeepIntvl(int fd) throws IOException; - public static native int getTcpKeepCnt(int fd) throws IOException; - public static native int getTcpUserTimeout(int milliseconds) throws IOException; - public static native int isIpFreeBind(int fd)throws IOException; - - public static native void setReuseAddress(int fd, int reuseAddress) throws IOException; - public static native void setReusePort(int fd, int reuseAddress) throws IOException; - public static native void setTcpFastopen(int fd, int tcpFastopenBacklog) throws IOException; - public static native void setTcpNotSentLowAt(int fd, int tcpNotSentLowAt) throws IOException; - public static native void setTrafficClass(int fd, int tcpNoDelay) throws IOException; - public static native void setBroadcast(int fd, int broadcast) throws IOException; - public static native void setTcpKeepIdle(int fd, int seconds) throws IOException; - public static native void setTcpKeepIntvl(int fd, int seconds) throws IOException; - public static native void setTcpKeepCnt(int fd, int probes) throws IOException; - public static native void setTcpUserTimeout(int fd, int milliseconds)throws IOException; - public static native void setIpFreeBind(int fd, int freeBind) throws IOException; - public static void tcpInfo(int fd, EpollTcpInfo info) throws IOException { - tcpInfo0(fd, info.info); - } - - private static native void tcpInfo0(int fd, int[] array) throws IOException; - - public static void setTcpMd5Sig(int fd, InetAddress address, byte[] key) throws IOException { - final NativeInetAddress a = NativeInetAddress.newInstance(address); - setTcpMd5Sig0(fd, a.address(), a.scopeId(), key); - } - - private static native void setTcpMd5Sig0(int fd, byte[] address, int scopeId, byte[] key) throws IOException; - // epoll_event related public static native int sizeofEpollEvent(); public static native int offsetofEpollData(); - private Native() { - // utility - } - private static void loadNativeLibrary() { String name = SystemPropertyUtil.get("os.name").toLowerCase(Locale.UK).trim(); if (!name.startsWith("linux")) { @@ -269,4 +191,8 @@ public final class Native { NativeLibraryLoader.load(SystemPropertyUtil.get("io.netty.packagePrefix", "").replace('.', '-') + "netty-transport-native-epoll", PlatformDependent.getClassLoader(Native.class)); } + + private Native() { + // utility + } } diff --git a/transport-native-epoll/src/main/java/io/netty/channel/epoll/NativeDatagramPacketArray.java b/transport-native-epoll/src/main/java/io/netty/channel/epoll/NativeDatagramPacketArray.java index 374f8a74ab..aafa67ee66 100644 --- a/transport-native-epoll/src/main/java/io/netty/channel/epoll/NativeDatagramPacketArray.java +++ b/transport-native-epoll/src/main/java/io/netty/channel/epoll/NativeDatagramPacketArray.java @@ -15,16 +15,18 @@ */ package io.netty.channel.epoll; -import static io.netty.channel.unix.NativeInetAddress.ipv4MappedIpv6Address; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelOutboundBuffer; import io.netty.channel.socket.DatagramPacket; +import io.netty.channel.unix.IovArray; import io.netty.util.concurrent.FastThreadLocal; - import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; +import static io.netty.channel.unix.Limits.UIO_MAX_IOV; +import static io.netty.channel.unix.NativeInetAddress.ipv4MappedIpv6Address; + /** * Support sendmmsg(...) on linux with GLIBC 2.14+ */ @@ -48,7 +50,7 @@ final class NativeDatagramPacketArray implements ChannelOutboundBuffer.MessagePr }; // Use UIO_MAX_IOV as this is the maximum number we can write with one sendmmsg(...) call. - private final NativeDatagramPacket[] packets = new NativeDatagramPacket[Native.UIO_MAX_IOV]; + private final NativeDatagramPacket[] packets = new NativeDatagramPacket[UIO_MAX_IOV]; private int count; private NativeDatagramPacketArray() { diff --git a/transport-native-epoll/src/main/java/io/netty/channel/epoll/TcpMd5Util.java b/transport-native-epoll/src/main/java/io/netty/channel/epoll/TcpMd5Util.java index 5eabdd21b3..080c835e30 100644 --- a/transport-native-epoll/src/main/java/io/netty/channel/epoll/TcpMd5Util.java +++ b/transport-native-epoll/src/main/java/io/netty/channel/epoll/TcpMd5Util.java @@ -55,7 +55,7 @@ final class TcpMd5Util { // Remove mappings not present in the new set. for (InetAddress addr : current) { if (!newKeys.containsKey(addr)) { - Native.setTcpMd5Sig(channel.fd().intValue(), addr, null); + channel.socket.setTcpMd5Sig(addr, null); } } @@ -66,7 +66,7 @@ final class TcpMd5Util { // Set new mappings and store addresses which we set. final Collection addresses = new ArrayList(newKeys.size()); for (Entry e : newKeys.entrySet()) { - Native.setTcpMd5Sig(channel.fd().intValue(), e.getKey(), e.getValue()); + channel.socket.setTcpMd5Sig(e.getKey(), e.getValue()); addresses.add(e.getKey()); } diff --git a/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollAbstractDomainSocketEchoTest.java b/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollAbstractDomainSocketEchoTest.java index ea0deb94a6..29855748c6 100644 --- a/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollAbstractDomainSocketEchoTest.java +++ b/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollAbstractDomainSocketEchoTest.java @@ -23,6 +23,6 @@ public class EpollAbstractDomainSocketEchoTest extends EpollDomainSocketEchoTest @Override protected SocketAddress newSocketAddress() { // these don't actually show up in the file system so creating a temp file isn't reliable - return new DomainSocketAddress("\0/tmp/" + UUID.randomUUID()); + return new DomainSocketAddress("\0" + System.getProperty("java.io.tmpdir") + UUID.randomUUID()); } } diff --git a/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSocketChannelConfigTest.java b/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSocketChannelConfigTest.java index 19ba3f84b8..3e7ab4dc13 100644 --- a/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSocketChannelConfigTest.java +++ b/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSocketChannelConfigTest.java @@ -26,7 +26,9 @@ import java.net.InetSocketAddress; import java.nio.channels.ClosedChannelException; import java.util.Random; +import org.junit.After; import org.junit.AfterClass; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -37,9 +39,18 @@ public class EpollSocketChannelConfigTest { private static Random rand; @BeforeClass - public static void before() { + public static void beforeClass() { rand = new Random(); group = new EpollEventLoopGroup(1); + } + + @AfterClass + public static void afterClass() { + group.shutdownGracefully(); + } + + @Before + public void setup() { Bootstrap bootstrap = new Bootstrap(); ch = (EpollSocketChannel) bootstrap.group(group) .channel(EpollSocketChannel.class) @@ -47,13 +58,9 @@ public class EpollSocketChannelConfigTest { .bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); } - @AfterClass - public static void after() { - try { - ch.close().syncUninterruptibly(); - } finally { - group.shutdownGracefully(); - } + @After + public void teardown() { + ch.close().syncUninterruptibly(); } private long randLong(long min, long max) { diff --git a/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSocketTest.java b/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSocketTest.java new file mode 100644 index 0000000000..4a094b7e86 --- /dev/null +++ b/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSocketTest.java @@ -0,0 +1,63 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.epoll; + +import io.netty.channel.unix.DomainSocketAddress; +import io.netty.channel.unix.PeerCredentials; +import io.netty.channel.unix.tests.SocketTest; +import io.netty.channel.unix.tests.UnixTestUtils; +import org.junit.Test; + +import java.io.IOException; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +public class EpollSocketTest extends SocketTest { + @Test + public void testTcpCork() throws Exception { + assertFalse(socket.isTcpCork()); + socket.setTcpCork(true); + assertTrue(socket.isTcpCork()); + } + + @Test + public void testPeerCreds() throws IOException { + LinuxSocket s1 = LinuxSocket.newSocketDomain(); + LinuxSocket s2 = LinuxSocket.newSocketDomain(); + + try { + DomainSocketAddress dsa = UnixTestUtils.newSocketAddress(); + s1.bind(dsa); + s1.listen(1); + + assertTrue(s2.connect(dsa)); + byte [] addr = new byte[64]; + s1.accept(addr); + PeerCredentials pc = s1.getPeerCredentials(); + assertNotEquals(pc.uid(), -1); + } finally { + s1.close(); + s2.close(); + } + } + + @Override + protected LinuxSocket newSocket() { + return LinuxSocket.newSocketStream(); + } +} diff --git a/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSocketTestPermutation.java b/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSocketTestPermutation.java index 93a9336f44..2da13e16ae 100644 --- a/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSocketTestPermutation.java +++ b/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSocketTestPermutation.java @@ -25,6 +25,7 @@ import io.netty.channel.socket.nio.NioDatagramChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.channel.unix.DomainSocketAddress; +import io.netty.channel.unix.tests.UnixTestUtils; import io.netty.testsuite.transport.TestsuitePermutation; import io.netty.testsuite.transport.TestsuitePermutation.BootstrapFactory; import io.netty.testsuite.transport.socket.SocketTestPermutation; @@ -214,12 +215,6 @@ class EpollSocketTestPermutation extends SocketTestPermutation { } public static DomainSocketAddress newSocketAddress() { - try { - File file = File.createTempFile("netty", "dsocket"); - file.delete(); - return new DomainSocketAddress(file); - } catch (IOException e) { - throw new IllegalStateException(e); - } + return UnixTestUtils.newSocketAddress(); } } diff --git a/transport-native-kqueue/pom.xml b/transport-native-kqueue/pom.xml new file mode 100644 index 0000000000..aa467c85ea --- /dev/null +++ b/transport-native-kqueue/pom.xml @@ -0,0 +1,197 @@ + + + + 4.0.0 + + io.netty + netty-parent + 4.1.11.Final-SNAPSHOT + + netty-transport-native-kqueue + + Netty/Transport/Native/KQueue + jar + + + + osx + + + mac + + + + LDFLAGS=-Wl,-weak_library,${unix.common.lib.unpacked.dir}/lib${unix.common.lib.name}.a + + + + + + + --add-exports java.base/sun.security.x509=ALL-UNNAMED --add-opens=java.base/java.nio=ALL-UNNAMED + netty-unix-common + ${project.build.directory}/unix-common-lib + ${unix.common.lib.dir}/META-INF/native/lib + ${unix.common.lib.dir}/META-INF/native/include + CFLAGS=-O3 -Werror -fno-omit-frame-pointer -Wunused-variable -I${unix.common.include.unpacked.dir} + LDFLAGS=-L${unix.common.lib.unpacked.dir} -Wl,--whole-archive -l${unix.common.lib.name} -Wl,--no-whole-archive + + + + + io.netty + netty-common + ${project.version} + + + io.netty + netty-buffer + ${project.version} + + + io.netty + netty-transport-native-unix-common + ${project.version} + ${jni.classifier} + + true + + + io.netty + netty-transport-native-unix-common + ${project.version} + + + io.netty + netty-transport + ${project.version} + + + io.netty + netty-testsuite + ${project.version} + test + + + io.netty + netty-transport-native-unix-common-tests + ${project.version} + test + + + ${project.groupId} + ${tcnative.artifactId} + ${tcnative.classifier} + test + + + + + + + maven-dependency-plugin + + + + unpack + generate-sources + + unpack-dependencies + + + ${project.groupId} + netty-transport-native-unix-common + ${jni.classifier} + ${unix.common.lib.dir} + META-INF/native/** + false + true + + + + + + + org.fusesource.hawtjni + maven-hawtjni-plugin + + + build-native-lib + + ${project.basedir}/src/main/c + ${project.build.outputDirectory} + + . + true + true + + ${jni.compiler.args.ldflags} + ${jni.compiler.args.cflags} + + + + generate + build + + compile + + + + + + maven-jar-plugin + + + + default-jar + + + META-INF/native/** + + + + + + native-jar + + jar + + + + + true + + + META-INF/native/libnetty-transport-native-kqueue.jnilib; osname=darwin, processor=x86_64" + + true + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + ${jni.classifier} + + + + + + + + diff --git a/transport-native-kqueue/src/main/c/netty_kqueue_bsdsocket.c b/transport-native-kqueue/src/main/c/netty_kqueue_bsdsocket.c new file mode 100644 index 0000000000..60b2c67508 --- /dev/null +++ b/transport-native-kqueue/src/main/c/netty_kqueue_bsdsocket.c @@ -0,0 +1,299 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "netty_unix_filedescriptor.h" +#include "netty_unix_socket.h" +#include "netty_unix_errors.h" +#include "netty_unix_util.h" +#include "netty_kqueue_bsdsocket.h" + +// Those are initialized in the init(...) method and cached for performance reasons +static jclass stringCls = NULL; +static jclass peerCredentialsClass = NULL; +static jfieldID fileChannelFieldId = NULL; +static jfieldID transferredFieldId = NULL; +static jfieldID fdFieldId = NULL; +static jfieldID fileDescriptorFieldId = NULL; +static jmethodID peerCredentialsMethodId = NULL; + +// JNI Registered Methods Begin +static jlong netty_kqueue_bsdsocket_sendFile(JNIEnv* env, jclass clazz, jint socketFd, jobject fileRegion, jlong base_off, jlong off, jlong len) { + jobject fileChannel = (*env)->GetObjectField(env, fileRegion, fileChannelFieldId); + if (fileChannel == NULL) { + netty_unix_errors_throwRuntimeException(env, "failed to get DefaultFileRegion.file"); + return -1; + } + jobject fileDescriptor = (*env)->GetObjectField(env, fileChannel, fileDescriptorFieldId); + if (fileDescriptor == NULL) { + netty_unix_errors_throwRuntimeException(env, "failed to get FileChannelImpl.fd"); + return -1; + } + jint srcFd = (*env)->GetIntField(env, fileDescriptor, fdFieldId); + if (srcFd == -1) { + netty_unix_errors_throwRuntimeException(env, "failed to get FileDescriptor.fd"); + return -1; + } + const jlong lenBefore = len; + off_t sbytes; + int res, err; + do { +#ifdef __APPLE__ + // sbytes is an input (how many to write) and output (how many were written) parameter. + sbytes = len; + res = sendfile(srcFd, socketFd, base_off + off, &sbytes, NULL, 0); +#else + sbytes = 0; + res = sendfile(srcFd, socketFd, base_off + off, len, NULL, &sbytes, 0); +#endif + len -= sbytes; + } while (res < 0 && ((err = errno) == EINTR)); + sbytes = lenBefore - len; + if (sbytes > 0) { + // update the transferred field in DefaultFileRegion + (*env)->SetLongField(env, fileRegion, transferredFieldId, off + sbytes); + return sbytes; + } + return res < 0 ? -err : 0; +} + +static void netty_kqueue_bsdsocket_setAcceptFilter(JNIEnv* env, jclass clazz, jint fd, jstring afName, jstring afArg) { +#ifdef SO_ACCEPTFILTER + struct accept_filter_arg af; + const char* tmpString = NULL; + af.af_name[0] = af.af_arg[0] ='\0'; + + tmpString = (*env)->GetStringUTFChars(env, afName, NULL); + strncat(af.af_name, tmpString, sizeof(af.af_name) / sizeof(af.af_name[0])); + (*env)->ReleaseStringUTFChars(env, afName, tmpString); + + tmpString = (*env)->GetStringUTFChars(env, afArg, NULL); + strncat(af.af_arg, tmpString, sizeof(af.af_arg) / sizeof(af.af_arg[0])); + (*env)->ReleaseStringUTFChars(env, afArg, tmpString); + + netty_unix_socket_setOption(env, fd, SOL_SOCKET, SO_ACCEPTFILTER, &af, sizeof(af)); +#else // No know replacement on MacOS + netty_unix_errors_throwChannelExceptionErrorNo(env, "setsockopt() failed: ", EINVAL); +#endif +} + +static jobjectArray netty_kqueue_bsdsocket_getAcceptFilter(JNIEnv* env, jclass clazz, jint fd) { +#ifdef SO_ACCEPTFILTER + struct accept_filter_arg af; + if (netty_unix_socket_getOption(env, fd, SOL_SOCKET, SO_ACCEPTFILTER, &af, sizeof(af)) == -1) { + netty_unix_errors_throwChannelExceptionErrorNo(env, "getsockopt() failed: ", errno); + return NULL; + } + jobjectArray resultArray = (*env)->NewObjectArray(env, 2, stringCls, NULL); + (*env)->SetObjectArrayElement(env, resultArray, 0, (*env)->NewStringUTF(env, &af.af_name[0])); + (*env)->SetObjectArrayElement(env, resultArray, 1, (*env)->NewStringUTF(env, &af.af_arg[0])); + return resultArray; +#else // No know replacement on MacOS + // Don't throw here because this is used when getting a list of all options. + return NULL; +#endif +} + +static void netty_kqueue_bsdsocket_setTcpNoPush(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_NOPUSH, &optval, sizeof(optval)); +} + +static void netty_kqueue_bsdsocket_setSndLowAt(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, SOL_SOCKET, SO_SNDLOWAT, &optval, sizeof(optval)); +} + +static jint netty_kqueue_bsdsocket_getTcpNoPush(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_NOPUSH, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jint netty_kqueue_bsdsocket_getSndLowAt(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, SOL_SOCKET, SO_SNDLOWAT, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + +static jobject netty_kqueue_bsdsocket_getPeerCredentials(JNIEnv *env, jclass clazz, jint fd) { + struct xucred credentials; + // It has been observed on MacOS that this method can complete successfully but not set all fields of xucred. + credentials.cr_ngroups = 0; + if(netty_unix_socket_getOption(env,fd, SOL_SOCKET, LOCAL_PEERCRED, &credentials, sizeof (credentials)) == -1) { + return NULL; + } + jintArray gids = NULL; + if (credentials.cr_ngroups > 1) { + gids = (*env)->NewIntArray(env, credentials.cr_ngroups); + (*env)->SetIntArrayRegion(env, gids, 0, credentials.cr_ngroups, (jint*) credentials.cr_groups); + } else { + // It has been observed on MacOS that cr_ngroups may not be set, but the cr_gid field is set. + gids = (*env)->NewIntArray(env, 1); + (*env)->SetIntArrayRegion(env, gids, 0, 1, (jint*) &credentials.cr_gid); + } + + // TODO: getting the PID may require reading/sending "ancillary data" via SCM_CREDENTIALS which is not desirable. + return (*env)->NewObject(env, peerCredentialsClass, peerCredentialsMethodId, 0, credentials.cr_uid, gids); +} +// JNI Registered Methods End + +// JNI Method Registration Table Begin +static const JNINativeMethod fixed_method_table[] = { + { "setAcceptFilter", "(ILjava/lang/String;Ljava/lang/String;)V", (void *) netty_kqueue_bsdsocket_setAcceptFilter }, + { "setTcpNoPush", "(II)V", (void *) netty_kqueue_bsdsocket_setTcpNoPush }, + { "setSndLowAt", "(II)V", (void *) netty_kqueue_bsdsocket_setSndLowAt }, + { "getAcceptFilter", "(I)[Ljava/lang/String;", (void *) netty_kqueue_bsdsocket_getAcceptFilter }, + { "getTcpNoPush", "(I)I", (void *) netty_kqueue_bsdsocket_getTcpNoPush }, + { "getSndLowAt", "(I)I", (void *) netty_kqueue_bsdsocket_getSndLowAt } +}; + +static const jint fixed_method_table_size = sizeof(fixed_method_table) / sizeof(fixed_method_table[0]); + +static jint dynamicMethodsTableSize() { + return fixed_method_table_size + 2; +} + +static JNINativeMethod* createDynamicMethodsTable(const char* packagePrefix) { + JNINativeMethod* dynamicMethods = malloc(sizeof(JNINativeMethod) * dynamicMethodsTableSize()); + memcpy(dynamicMethods, fixed_method_table, sizeof(fixed_method_table)); + char* dynamicTypeName = netty_unix_util_prepend(packagePrefix, "io/netty/channel/DefaultFileRegion;JJJ)J"); + JNINativeMethod* dynamicMethod = &dynamicMethods[fixed_method_table_size]; + dynamicMethod->name = "sendFile"; + dynamicMethod->signature = netty_unix_util_prepend("(IL", dynamicTypeName); + dynamicMethod->fnPtr = (void *) netty_kqueue_bsdsocket_sendFile; + free(dynamicTypeName); + + ++dynamicMethod; + dynamicTypeName = netty_unix_util_prepend(packagePrefix, "io/netty/channel/unix/PeerCredentials;"); + dynamicMethod->name = "getPeerCredentials"; + dynamicMethod->signature = netty_unix_util_prepend("(I)L", dynamicTypeName); + dynamicMethod->fnPtr = (void *) netty_kqueue_bsdsocket_getPeerCredentials; + free(dynamicTypeName); + + return dynamicMethods; +} + +static void freeDynamicMethodsTable(JNINativeMethod* dynamicMethods) { + jint fullMethodTableSize = dynamicMethodsTableSize(); + jint i = fixed_method_table_size; + for (; i < fullMethodTableSize; ++i) { + free(dynamicMethods[i].signature); + } + free(dynamicMethods); +} +// JNI Method Registration Table End + +jint netty_kqueue_bsdsocket_JNI_OnLoad(JNIEnv* env, const char* packagePrefix) { + // Register the methods which are not referenced by static member variables + JNINativeMethod* dynamicMethods = createDynamicMethodsTable(packagePrefix); + if (netty_unix_util_register_natives(env, + packagePrefix, + "io/netty/channel/kqueue/BsdSocket", + dynamicMethods, + dynamicMethodsTableSize()) != 0) { + freeDynamicMethodsTable(dynamicMethods); + return JNI_ERR; + } + freeDynamicMethodsTable(dynamicMethods); + dynamicMethods = NULL; + + // Initialize this module + char* nettyClassName = netty_unix_util_prepend(packagePrefix, "io/netty/channel/DefaultFileRegion"); + jclass fileRegionCls = (*env)->FindClass(env, nettyClassName); + free(nettyClassName); + nettyClassName = NULL; + if (fileRegionCls == NULL) { + return JNI_ERR; + } + fileChannelFieldId = (*env)->GetFieldID(env, fileRegionCls, "file", "Ljava/nio/channels/FileChannel;"); + if (fileChannelFieldId == NULL) { + netty_unix_errors_throwRuntimeException(env, "failed to get field ID: DefaultFileRegion.file"); + return JNI_ERR; + } + transferredFieldId = (*env)->GetFieldID(env, fileRegionCls, "transferred", "J"); + if (transferredFieldId == NULL) { + netty_unix_errors_throwRuntimeException(env, "failed to get field ID: DefaultFileRegion.transferred"); + return JNI_ERR; + } + + jclass fileChannelCls = (*env)->FindClass(env, "sun/nio/ch/FileChannelImpl"); + if (fileChannelCls == NULL) { + // pending exception... + return JNI_ERR; + } + fileDescriptorFieldId = (*env)->GetFieldID(env, fileChannelCls, "fd", "Ljava/io/FileDescriptor;"); + if (fileDescriptorFieldId == NULL) { + netty_unix_errors_throwRuntimeException(env, "failed to get field ID: FileChannelImpl.fd"); + return JNI_ERR; + } + + jclass fileDescriptorCls = (*env)->FindClass(env, "java/io/FileDescriptor"); + if (fileDescriptorCls == NULL) { + // pending exception... + return JNI_ERR; + } + fdFieldId = (*env)->GetFieldID(env, fileDescriptorCls, "fd", "I"); + if (fdFieldId == NULL) { + netty_unix_errors_throwRuntimeException(env, "failed to get field ID: FileDescriptor.fd"); + return JNI_ERR; + } + stringCls = (*env)->FindClass(env, "java/lang/String"); + if (stringCls == NULL) { + // pending exception... + return JNI_ERR; + } + + nettyClassName = netty_unix_util_prepend(packagePrefix, "io/netty/channel/unix/PeerCredentials"); + jclass localPeerCredsClass = (*env)->FindClass(env, nettyClassName); + free(nettyClassName); + nettyClassName = NULL; + if (localPeerCredsClass == NULL) { + // pending exception... + return JNI_ERR; + } + peerCredentialsClass = (jclass) (*env)->NewGlobalRef(env, localPeerCredsClass); + if (peerCredentialsClass == NULL) { + // out-of-memory! + netty_unix_errors_throwOutOfMemoryError(env); + return JNI_ERR; + } + peerCredentialsMethodId = (*env)->GetMethodID(env, peerCredentialsClass, "", "(II[I)V"); + if (peerCredentialsMethodId == NULL) { + netty_unix_errors_throwRuntimeException(env, "failed to get method ID: PeerCredentials.(int, int, int[])"); + return JNI_ERR; + } + + return JNI_VERSION_1_6; +} + +void netty_kqueue_bsdsocket_JNI_OnUnLoad(JNIEnv* env) { + if (peerCredentialsClass != NULL) { + (*env)->DeleteGlobalRef(env, peerCredentialsClass); + peerCredentialsClass = NULL; + } +} diff --git a/transport-native-kqueue/src/main/c/netty_kqueue_bsdsocket.h b/transport-native-kqueue/src/main/c/netty_kqueue_bsdsocket.h new file mode 100644 index 0000000000..a7984de57c --- /dev/null +++ b/transport-native-kqueue/src/main/c/netty_kqueue_bsdsocket.h @@ -0,0 +1,25 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +#ifndef NETTY_KQUEUE_BSDSOCKET_H_ +#define NETTY_KQUEUE_BSDSOCKET_H_ + +#include + +// JNI initialization hooks. Users of this file are responsible for calling these in the JNI_OnLoad and JNI_OnUnload methods. +jint netty_kqueue_bsdsocket_JNI_OnLoad(JNIEnv* env, const char* packagePrefix); +void netty_kqueue_bsdsocket_JNI_OnUnLoad(JNIEnv* env); + +#endif /* NETTY_KQUEUE_BSDSOCKET_H_ */ diff --git a/transport-native-kqueue/src/main/c/netty_kqueue_eventarray.c b/transport-native-kqueue/src/main/c/netty_kqueue_eventarray.c new file mode 100644 index 0000000000..704ef084c7 --- /dev/null +++ b/transport-native-kqueue/src/main/c/netty_kqueue_eventarray.c @@ -0,0 +1,124 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +#include +#include +#include +#include +#include +#include "netty_unix_util.h" +#include "netty_unix_errors.h" +#include "netty_kqueue_eventarray.h" + +jfieldID kqueueJniPtrFieldId = NULL; + +static void netty_kqueue_eventarray_evSet(JNIEnv* env, jclass clzz, jlong keventAddress, jobject channel, jint ident, jshort filter, jshort flags, jint fflags) { + // Create a global pointer, cast it as a long, and retain it in java to re-use and free later. + jlong jniSelfPtr = (*env)->GetLongField(env, channel, kqueueJniPtrFieldId); + if (jniSelfPtr == 0) { + jniSelfPtr = (jlong) (*env)->NewGlobalRef(env, channel); + (*env)->SetLongField(env, channel, kqueueJniPtrFieldId, jniSelfPtr); + } + EV_SET((struct kevent*) keventAddress, ident, filter, flags, fflags, 0, (jobject) jniSelfPtr); +} + +static jobject netty_kqueue_eventarray_getChannel(JNIEnv* env, jclass clazz, jlong keventAddress) { + struct kevent* event = (struct kevent*) keventAddress; + return event->udata == NULL ? NULL : (jobject) event->udata; +} + +static void netty_kqueue_eventarray_deleteGlobalRefs(JNIEnv* env, jclass clazz, jlong channelAddressStart, jlong channelAddressEnd) { + // Iterate over an array of longs, which are really pointers to the jobject NewGlobalRef created above in evSet + // and delete each one. The field has already been set to 0 in java. + jlong* itr = (jlong*) channelAddressStart; + const jlong* end = (jlong*) channelAddressEnd; + for (; itr != end; ++itr) { + (*env)->DeleteGlobalRef(env, (jobject) *itr); + } +} + +// JNI Method Registration Table Begin +static const JNINativeMethod fixed_method_table[] = { + { "deleteGlobalRefs", "(JJ)V", (void *) netty_kqueue_eventarray_deleteGlobalRefs } + // "evSet" has a dynamic signature + // "getChannel" has a dynamic signature +}; +static const jint fixed_method_table_size = sizeof(fixed_method_table) / sizeof(fixed_method_table[0]); + +static jint dynamicMethodsTableSize() { + return fixed_method_table_size + 2; +} + +static JNINativeMethod* createDynamicMethodsTable(const char* packagePrefix) { + JNINativeMethod* dynamicMethods = malloc(sizeof(JNINativeMethod) * dynamicMethodsTableSize()); + memcpy(dynamicMethods, fixed_method_table, sizeof(fixed_method_table)); + char* dynamicTypeName = netty_unix_util_prepend(packagePrefix, "io/netty/channel/kqueue/AbstractKQueueChannel;ISSI)V"); + JNINativeMethod* dynamicMethod = &dynamicMethods[fixed_method_table_size]; + dynamicMethod->name = "evSet"; + dynamicMethod->signature = netty_unix_util_prepend("(JL", dynamicTypeName); + dynamicMethod->fnPtr = (void *) netty_kqueue_eventarray_evSet; + free(dynamicTypeName); + + ++dynamicMethod; + dynamicTypeName = netty_unix_util_prepend(packagePrefix, "io/netty/channel/kqueue/AbstractKQueueChannel;"); + dynamicMethod->name = "getChannel"; + dynamicMethod->signature = netty_unix_util_prepend("(J)L", dynamicTypeName); + dynamicMethod->fnPtr = (void *) netty_kqueue_eventarray_getChannel; + free(dynamicTypeName); + return dynamicMethods; +} + +static void freeDynamicMethodsTable(JNINativeMethod* dynamicMethods) { + jint fullMethodTableSize = dynamicMethodsTableSize(); + jint i = fixed_method_table_size; + for (; i < fullMethodTableSize; ++i) { + free(dynamicMethods[i].signature); + } + free(dynamicMethods); +} +// JNI Method Registration Table End + +jint netty_kqueue_eventarray_JNI_OnLoad(JNIEnv* env, const char* packagePrefix) { + JNINativeMethod* dynamicMethods = createDynamicMethodsTable(packagePrefix); + if (netty_unix_util_register_natives(env, + packagePrefix, + "io/netty/channel/kqueue/KQueueEventArray", + dynamicMethods, + dynamicMethodsTableSize()) != 0) { + freeDynamicMethodsTable(dynamicMethods); + return JNI_ERR; + } + freeDynamicMethodsTable(dynamicMethods); + dynamicMethods = NULL; + + char* nettyClassName = netty_unix_util_prepend(packagePrefix, "io/netty/channel/kqueue/AbstractKQueueChannel"); + jclass kqueueChannelCls = (*env)->FindClass(env, nettyClassName); + free(nettyClassName); + nettyClassName = NULL; + if (kqueueChannelCls == NULL) { + return JNI_ERR; + } + + kqueueJniPtrFieldId = (*env)->GetFieldID(env, kqueueChannelCls, "jniSelfPtr", "J"); + if (kqueueJniPtrFieldId == NULL) { + netty_unix_errors_throwRuntimeException(env, "failed to get field ID: AbstractKQueueChannel.jniSelfPtr"); + return JNI_ERR; + } + + return JNI_VERSION_1_6; +} + +void netty_kqueue_eventarray_JNI_OnUnLoad(JNIEnv* env) { +} diff --git a/transport-native-kqueue/src/main/c/netty_kqueue_eventarray.h b/transport-native-kqueue/src/main/c/netty_kqueue_eventarray.h new file mode 100644 index 0000000000..ec3b08371a --- /dev/null +++ b/transport-native-kqueue/src/main/c/netty_kqueue_eventarray.h @@ -0,0 +1,25 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +#ifndef NETTY_KQUEUE_EVENTARRAY_H_ +#define NETTY_KQUEUE_EVENTARRAY_H_ + +#include + +// JNI initialization hooks. Users of this file are responsible for calling these in the JNI_OnLoad and JNI_OnUnload methods. +jint netty_kqueue_eventarray_JNI_OnLoad(JNIEnv* env, const char* packagePrefix); +void netty_kqueue_eventarray_JNI_OnUnLoad(JNIEnv* env); + +#endif /* NETTY_KQUEUE_EVENTARRAY_H_ */ diff --git a/transport-native-kqueue/src/main/c/netty_kqueue_native.c b/transport-native-kqueue/src/main/c/netty_kqueue_native.c new file mode 100644 index 0000000000..c080d0eb49 --- /dev/null +++ b/transport-native-kqueue/src/main/c/netty_kqueue_native.c @@ -0,0 +1,307 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "netty_unix_filedescriptor.h" +#include "netty_unix_socket.h" +#include "netty_unix_errors.h" +#include "netty_unix_util.h" +#include "netty_unix_limits.h" +#include "netty_kqueue_bsdsocket.h" +#include "netty_kqueue_eventarray.h" + +clockid_t waitClockId = 0; // initialized by netty_unix_util_initialize_wait_clock + +static jint netty_kqueue_native_kqueueCreate(JNIEnv* env, jclass clazz) { + jint kq = kqueue(); + if (kq < 0) { + netty_unix_errors_throwChannelExceptionErrorNo(env, "kqueue() failed: ", errno); + } + return kq; +} + +static jint netty_kqueue_native_keventChangeSingleUserEvent(jint kqueueFd, struct kevent* userEvent) { + int result, err; + result = kevent(kqueueFd, userEvent, 1, NULL, 0, NULL); + if (result < 0) { + // https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2 + // When kevent() call fails with EINTR error, all changes in the changelist have been applied. + return (err = errno) == EINTR ? 0 : -err; + } + + return result; +} + +static jint netty_kqueue_native_keventTriggerUserEvent(JNIEnv* env, jclass clazz, jint kqueueFd, jint ident) { + struct kevent userEvent; + EV_SET(&userEvent, ident, EVFILT_USER, 0, NOTE_TRIGGER | NOTE_FFNOP, 0, NULL); + return netty_kqueue_native_keventChangeSingleUserEvent(kqueueFd, &userEvent); +} + +static jint netty_kqueue_native_keventAddUserEvent(JNIEnv* env, jclass clazz, jint kqueueFd, jint ident) { + struct kevent userEvent; + EV_SET(&userEvent, ident, EVFILT_USER, EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_FFNOP, 0, NULL); + return netty_kqueue_native_keventChangeSingleUserEvent(kqueueFd, &userEvent); +} + +static jint netty_kqueue_native_keventWait(JNIEnv* env, jclass clazz, jint kqueueFd, jlong changeListAddress, jint changeListLength, + jlong eventListAddress, jint eventListLength, jint tvSec, jint tvNsec) { + struct kevent* changeList = (struct kevent*) changeListAddress; + struct kevent* eventList = (struct kevent*) eventListAddress; + struct timespec beforeTs, nowTs, timeoutTs; + int result, err; + + timeoutTs.tv_sec = tvSec; + timeoutTs.tv_nsec = tvNsec; + + // Negatives = wait indefinitely, Zeros = poll (aka return immediately). + if ((tvSec == 0 && tvNsec == 0) || tvSec < 0 || tvNsec < 0) { + const struct timespec* fixedTs = (tvSec == 0 && tvNsec == 0) ? &timeoutTs : NULL; + for (;;) { + result = kevent(kqueueFd, changeList, changeListLength, eventList, eventListLength, fixedTs); + if (result >= 0) { + return result; + } + if ((err = errno) != EINTR) { + return -err; + } + + // https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2 + // When kevent() call fails with EINTR error, all changes in the changelist have been applied. + changeListLength = 0; + } + } + + // Wait with a timeout value. + netty_unix_util_clock_gettime(waitClockId, &beforeTs); + for (;;) { + result = kevent(kqueueFd, changeList, changeListLength, eventList, eventListLength, &timeoutTs); + if (result >= 0) { + return result; + } + if ((err = errno) != EINTR) { + return -err; + } + + netty_unix_util_clock_gettime(waitClockId, &nowTs); + // beforeTs will store the time difference to check for overflow + beforeTs.tv_sec = nowTs.tv_sec - beforeTs.tv_sec; + beforeTs.tv_nsec = nowTs.tv_nsec - beforeTs.tv_nsec; + // Now subtract the time difference + timeoutTs.tv_sec -= beforeTs.tv_sec; + timeoutTs.tv_nsec -= beforeTs.tv_nsec; + if (beforeTs.tv_sec < 0 || beforeTs.tv_nsec < 0 || (timeoutTs.tv_sec <= 0 && timeoutTs.tv_nsec <= 0)) { + return 0; + } + beforeTs = nowTs; + // https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2 + // When kevent() call fails with EINTR error, all changes in the changelist have been applied. + changeListLength = 0; + } +} + +static jint netty_kqueue_native_sizeofKEvent(JNIEnv* env, jclass clazz) { + return sizeof(struct kevent); +} + +static jint netty_kqueue_native_offsetofKEventIdent(JNIEnv* env, jclass clazz) { + return offsetof(struct kevent, ident); +} + +static jint netty_kqueue_native_offsetofKEventFlags(JNIEnv* env, jclass clazz) { + return offsetof(struct kevent, flags); +} + +static jint netty_kqueue_native_offsetofKEventFilter(JNIEnv* env, jclass clazz) { + return offsetof(struct kevent, filter); +} + +static jint netty_kqueue_native_offsetofKEventFFlags(JNIEnv* env, jclass clazz) { + return offsetof(struct kevent, fflags); +} + +static jint netty_kqueue_native_offsetofKeventData(JNIEnv* env, jclass clazz) { + return offsetof(struct kevent, data); +} + +static jshort netty_kqueue_native_evfiltRead(JNIEnv* env, jclass clazz) { + return EVFILT_READ; +} + +static jshort netty_kqueue_native_evfiltWrite(JNIEnv* env, jclass clazz) { + return EVFILT_WRITE; +} + +static jshort netty_kqueue_native_evfiltUser(JNIEnv* env, jclass clazz) { + return EVFILT_USER; +} + +static jshort netty_kqueue_native_evAdd(JNIEnv* env, jclass clazz) { + return EV_ADD; +} + +static jshort netty_kqueue_native_evEnable(JNIEnv* env, jclass clazz) { + return EV_ENABLE; +} + +static jshort netty_kqueue_native_evDisable(JNIEnv* env, jclass clazz) { + return EV_DISABLE; +} + +static jshort netty_kqueue_native_evDelete(JNIEnv* env, jclass clazz) { + return EV_DELETE; +} + +static jshort netty_kqueue_native_evClear(JNIEnv* env, jclass clazz) { + return EV_CLEAR; +} + +static jshort netty_kqueue_native_evEOF(JNIEnv* env, jclass clazz) { + return EV_EOF; +} + +static jshort netty_kqueue_native_evError(JNIEnv* env, jclass clazz) { + return EV_ERROR; +} + +// JNI Method Registration Table Begin +static const JNINativeMethod statically_referenced_fixed_method_table[] = { + { "evfiltRead", "()S", (void *) netty_kqueue_native_evfiltRead }, + { "evfiltWrite", "()S", (void *) netty_kqueue_native_evfiltWrite }, + { "evfiltUser", "()S", (void *) netty_kqueue_native_evfiltUser }, + { "evAdd", "()S", (void *) netty_kqueue_native_evAdd }, + { "evEnable", "()S", (void *) netty_kqueue_native_evEnable }, + { "evDisable", "()S", (void *) netty_kqueue_native_evDisable }, + { "evDelete", "()S", (void *) netty_kqueue_native_evDelete }, + { "evClear", "()S", (void *) netty_kqueue_native_evClear }, + { "evEOF", "()S", (void *) netty_kqueue_native_evEOF }, + { "evError", "()S", (void *) netty_kqueue_native_evError } +}; +static const jint statically_referenced_fixed_method_table_size = sizeof(statically_referenced_fixed_method_table) / sizeof(statically_referenced_fixed_method_table[0]); +static const JNINativeMethod fixed_method_table[] = { + { "kqueueCreate", "()I", (void *) netty_kqueue_native_kqueueCreate }, + { "keventTriggerUserEvent", "(II)I", (void *) netty_kqueue_native_keventTriggerUserEvent }, + { "keventAddUserEvent", "(II)I", (void *) netty_kqueue_native_keventAddUserEvent }, + { "keventWait", "(IJIJIII)I", (void *) netty_kqueue_native_keventWait }, + { "sizeofKEvent", "()I", (void *) netty_kqueue_native_sizeofKEvent }, + { "offsetofKEventIdent", "()I", (void *) netty_kqueue_native_offsetofKEventIdent }, + { "offsetofKEventFlags", "()I", (void *) netty_kqueue_native_offsetofKEventFlags }, + { "offsetofKEventFFlags", "()I", (void *) netty_kqueue_native_offsetofKEventFFlags }, + { "offsetofKEventFilter", "()I", (void *) netty_kqueue_native_offsetofKEventFilter }, + { "offsetofKeventData", "()I", (void *) netty_kqueue_native_offsetofKeventData } +}; +static const jint fixed_method_table_size = sizeof(fixed_method_table) / sizeof(fixed_method_table[0]); +// JNI Method Registration Table End + +static jint netty_kqueue_native_JNI_OnLoad(JNIEnv* env, const char* packagePrefix) { + // We must register the statically referenced methods first! + if (netty_unix_util_register_natives(env, + packagePrefix, + "io/netty/channel/kqueue/KQueueStaticallyReferencedJniMethods", + statically_referenced_fixed_method_table, + statically_referenced_fixed_method_table_size) != 0) { + return JNI_ERR; + } + // Register the methods which are not referenced by static member variables + if (netty_unix_util_register_natives(env, packagePrefix, "io/netty/channel/kqueue/Native", fixed_method_table, fixed_method_table_size) != 0) { + return JNI_ERR; + } + // Load all c modules that we depend upon + if (netty_unix_limits_JNI_OnLoad(env, packagePrefix) == JNI_ERR) { + return JNI_ERR; + } + if (netty_unix_errors_JNI_OnLoad(env, packagePrefix) == JNI_ERR) { + return JNI_ERR; + } + if (netty_unix_filedescriptor_JNI_OnLoad(env, packagePrefix) == JNI_ERR) { + return JNI_ERR; + } + if (netty_unix_socket_JNI_OnLoad(env, packagePrefix) == JNI_ERR) { + return JNI_ERR; + } + if (netty_kqueue_bsdsocket_JNI_OnLoad(env, packagePrefix) == JNI_ERR) { + return JNI_ERR; + } + if (netty_kqueue_eventarray_JNI_OnLoad(env, packagePrefix) == JNI_ERR) { + return JNI_ERR; + } + // Initialize this module + + if (!netty_unix_util_initialize_wait_clock(&waitClockId)) { + fprintf(stderr, "FATAL: could not find a clock for clock_gettime!\n"); + return JNI_ERR; + } + + return JNI_VERSION_1_6; +} + +static void netty_kqueue_native_JNI_OnUnLoad(JNIEnv* env) { + netty_unix_limits_JNI_OnUnLoad(env); + netty_unix_errors_JNI_OnUnLoad(env); + netty_unix_filedescriptor_JNI_OnUnLoad(env); + netty_unix_socket_JNI_OnUnLoad(env); + netty_kqueue_bsdsocket_JNI_OnUnLoad(env); + netty_kqueue_eventarray_JNI_OnUnLoad(env); +} + +jint JNI_OnLoad(JavaVM* vm, void* reserved) { + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6) != JNI_OK) { + return JNI_ERR; + } + + Dl_info dlinfo; + jint status = 0; + // We need to use an address of a function that is uniquely part of this library, so choose a static + // function. See https://github.com/netty/netty/issues/4840. + if (!dladdr((void*) netty_kqueue_native_JNI_OnUnLoad, &dlinfo)) { + fprintf(stderr, "FATAL: transport-native-kqueue JNI call to dladdr failed!\n"); + return JNI_ERR; + } + char* packagePrefix = netty_unix_util_parse_package_prefix(dlinfo.dli_fname, "netty-transport-native-kqueue", &status); + if (status == JNI_ERR) { + fprintf(stderr, "FATAL: transport-native-kqueue JNI encountered unexpected dlinfo.dli_fname: %s\n", dlinfo.dli_fname); + return JNI_ERR; + } + + jint ret = netty_kqueue_native_JNI_OnLoad(env, packagePrefix); + + if (packagePrefix != NULL) { + free(packagePrefix); + packagePrefix = NULL; + } + + return ret; +} + +void JNI_OnUnload(JavaVM* vm, void* reserved) { + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6) != JNI_OK) { + // Something is wrong but nothing we can do about this :( + return; + } + netty_kqueue_native_JNI_OnUnLoad(env); +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/AbstractKQueueChannel.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/AbstractKQueueChannel.java new file mode 100644 index 0000000000..1105d4f9c1 --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/AbstractKQueueChannel.java @@ -0,0 +1,470 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.AbstractChannel; +import io.netty.channel.Channel; +import io.netty.channel.ChannelConfig; +import io.netty.channel.ChannelMetadata; +import io.netty.channel.EventLoop; +import io.netty.channel.RecvByteBufAllocator; +import io.netty.channel.socket.ChannelInputShutdownEvent; +import io.netty.channel.socket.ChannelInputShutdownReadComplete; +import io.netty.channel.unix.FileDescriptor; +import io.netty.channel.unix.UnixChannel; +import io.netty.util.ReferenceCountUtil; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.NotYetConnectedException; +import java.nio.channels.UnresolvedAddressException; + +import static io.netty.util.internal.ObjectUtil.checkNotNull; + +abstract class AbstractKQueueChannel extends AbstractChannel implements UnixChannel { + private static final ChannelMetadata METADATA = new ChannelMetadata(false); + final BsdSocket socket; + private boolean readFilterEnabled = true; + private boolean writeFilterEnabled; + boolean readReadyRunnablePending; + boolean inputClosedSeenErrorOnRead; + /** + * This member variable means we don't have to have a map in {@link KQueueEventLoop} which associates the FDs + * from kqueue to instances of this class. This field will be initialized by JNI when modifying kqueue events. + * If there is no global reference when JNI gets a kqueue evSet call (aka this field is 0) then a global reference + * will be created and the address will be saved in this member variable. Then when we process a kevent in Java + * we can ask JNI to give us the {@link AbstractKQueueChannel} that corresponds to that event. + */ + long jniSelfPtr; + + protected volatile boolean active; + + AbstractKQueueChannel(Channel parent, BsdSocket fd, boolean active) { + this(parent, fd, active, false); + } + + AbstractKQueueChannel(Channel parent, BsdSocket fd, boolean active, boolean writeFilterEnabled) { + super(parent); + socket = checkNotNull(fd, "fd"); + this.active = active; + this.writeFilterEnabled = writeFilterEnabled; + } + + @Override + public final FileDescriptor fd() { + return socket; + } + + @Override + public boolean isActive() { + return active; + } + + @Override + public ChannelMetadata metadata() { + return METADATA; + } + + @Override + protected void doClose() throws Exception { + active = false; + // Even if we allow half closed sockets we should give up on reading. Otherwise we may allow a read attempt on a + // socket which has not even been connected yet. This has been observed to block during unit tests. + inputClosedSeenErrorOnRead = true; + // The FD will be closed, which will take of deleting from kqueue. + readFilterEnabled = writeFilterEnabled = false; + try { + ((KQueueEventLoop) eventLoop()).remove(this); + } finally { + socket.close(); + } + } + + @Override + protected void doDisconnect() throws Exception { + doClose(); + } + + @Override + protected boolean isCompatible(EventLoop loop) { + return loop instanceof KQueueEventLoop; + } + + @Override + public boolean isOpen() { + return socket.isOpen(); + } + + @Override + protected void doDeregister() throws Exception { + // Make sure we unregister our filters from kqueue! + readFilter(false); + writeFilter(false); + + ((KQueueEventLoop) eventLoop()).remove(this); + + // Set the filters back to the initial state in case this channel is registered with another event loop. + readFilterEnabled = true; + } + + @Override + protected final void doBeginRead() throws Exception { + // Channel.read() or ChannelHandlerContext.read() was called + final AbstractKQueueUnsafe unsafe = (AbstractKQueueUnsafe) unsafe(); + unsafe.readPending = true; + + // We must set the read flag here as it is possible the user didn't read in the last read loop, the + // executeReadReadyRunnable could read nothing, and if the user doesn't explicitly call read they will + // never get data after this. + readFilter(true); + + // If auto read was toggled off on the last read loop then we may not be notified + // again if we didn't consume all the data. So we force a read operation here if there maybe more data. + if (unsafe.maybeMoreDataToRead) { + unsafe.executeReadReadyRunnable(config()); + } + } + + @Override + protected void doRegister() throws Exception { + // Just in case the previous EventLoop was shutdown abruptly, or an event is still pending on the old EventLoop + // make sure the readReadyRunnablePending variable is reset so we will be able to execute the Runnable on the + // new EventLoop. + readReadyRunnablePending = false; + // Add the write event first so we get notified of connection refused on the client side! + if (writeFilterEnabled) { + evSet0(Native.EVFILT_WRITE, Native.EV_ADD_CLEAR_ENABLE); + } + if (readFilterEnabled) { + evSet0(Native.EVFILT_READ, Native.EV_ADD_CLEAR_ENABLE); + } + } + + @Override + protected abstract AbstractKQueueUnsafe newUnsafe(); + + @Override + public abstract KQueueChannelConfig config(); + + /** + * Returns an off-heap copy of the specified {@link ByteBuf}, and releases the original one. + */ + protected final ByteBuf newDirectBuffer(ByteBuf buf) { + return newDirectBuffer(buf, buf); + } + + /** + * Returns an off-heap copy of the specified {@link ByteBuf}, and releases the specified holder. + * The caller must ensure that the holder releases the original {@link ByteBuf} when the holder is released by + * this method. + */ + protected final ByteBuf newDirectBuffer(Object holder, ByteBuf buf) { + final int readableBytes = buf.readableBytes(); + if (readableBytes == 0) { + ReferenceCountUtil.safeRelease(holder); + return Unpooled.EMPTY_BUFFER; + } + + final ByteBufAllocator alloc = alloc(); + if (alloc.isDirectBufferPooled()) { + return newDirectBuffer0(holder, buf, alloc, readableBytes); + } + + final ByteBuf directBuf = ByteBufUtil.threadLocalDirectBuffer(); + if (directBuf == null) { + return newDirectBuffer0(holder, buf, alloc, readableBytes); + } + + directBuf.writeBytes(buf, buf.readerIndex(), readableBytes); + ReferenceCountUtil.safeRelease(holder); + return directBuf; + } + + private static ByteBuf newDirectBuffer0(Object holder, ByteBuf buf, ByteBufAllocator alloc, int capacity) { + final ByteBuf directBuf = alloc.directBuffer(capacity); + directBuf.writeBytes(buf, buf.readerIndex(), capacity); + ReferenceCountUtil.safeRelease(holder); + return directBuf; + } + + protected static void checkResolvable(InetSocketAddress addr) { + if (addr.isUnresolved()) { + throw new UnresolvedAddressException(); + } + } + + /** + * Read bytes into the given {@link ByteBuf} and return the amount. + */ + protected final int doReadBytes(ByteBuf byteBuf) throws Exception { + int writerIndex = byteBuf.writerIndex(); + int localReadAmount; + unsafe().recvBufAllocHandle().attemptedBytesRead(byteBuf.writableBytes()); + if (byteBuf.hasMemoryAddress()) { + localReadAmount = socket.readAddress(byteBuf.memoryAddress(), writerIndex, byteBuf.capacity()); + } else { + ByteBuffer buf = byteBuf.internalNioBuffer(writerIndex, byteBuf.writableBytes()); + localReadAmount = socket.read(buf, buf.position(), buf.limit()); + } + if (localReadAmount > 0) { + byteBuf.writerIndex(writerIndex + localReadAmount); + } + return localReadAmount; + } + + protected final int doWriteBytes(ByteBuf buf, int writeSpinCount) throws Exception { + int readableBytes = buf.readableBytes(); + int writtenBytes = 0; + if (buf.hasMemoryAddress()) { + long memoryAddress = buf.memoryAddress(); + int readerIndex = buf.readerIndex(); + int writerIndex = buf.writerIndex(); + for (int i = writeSpinCount; i > 0; --i) { + int localFlushedAmount = socket.writeAddress(memoryAddress, readerIndex, writerIndex); + if (localFlushedAmount > 0) { + writtenBytes += localFlushedAmount; + if (writtenBytes == readableBytes) { + return writtenBytes; + } + readerIndex += localFlushedAmount; + } else { + break; + } + } + } else { + ByteBuffer nioBuf; + if (buf.nioBufferCount() == 1) { + nioBuf = buf.internalNioBuffer(buf.readerIndex(), buf.readableBytes()); + } else { + nioBuf = buf.nioBuffer(); + } + for (int i = writeSpinCount; i > 0; --i) { + int pos = nioBuf.position(); + int limit = nioBuf.limit(); + int localFlushedAmount = socket.write(nioBuf, pos, limit); + if (localFlushedAmount > 0) { + nioBuf.position(pos + localFlushedAmount); + writtenBytes += localFlushedAmount; + if (writtenBytes == readableBytes) { + return writtenBytes; + } + } else { + break; + } + } + } + if (writtenBytes < readableBytes) { + // Returned EAGAIN need to wait until we are allowed to write again. + writeFilter(true); + } + return writtenBytes; + } + + final boolean shouldBreakReadReady(ChannelConfig config) { + return socket.isInputShutdown() && (inputClosedSeenErrorOnRead || !isAllowHalfClosure(config)); + } + + final boolean isAllowHalfClosure(ChannelConfig config) { + return config instanceof KQueueSocketChannelConfig && + ((KQueueSocketChannelConfig) config).isAllowHalfClosure(); + } + + final void clearReadFilter() { + // Only clear if registered with an EventLoop as otherwise + if (isRegistered()) { + final EventLoop loop = eventLoop(); + final AbstractKQueueUnsafe unsafe = (AbstractKQueueUnsafe) unsafe(); + if (loop.inEventLoop()) { + unsafe.clearReadFilter0(); + } else { + // schedule a task to clear the EPOLLIN as it is not safe to modify it directly + loop.execute(new Runnable() { + @Override + public void run() { + if (!unsafe.readPending && !config().isAutoRead()) { + // Still no read triggered so clear it now + unsafe.clearReadFilter0(); + } + } + }); + } + } else { + // The EventLoop is not registered atm so just update the flags so the correct value + // will be used once the channel is registered + readFilterEnabled = false; + } + } + + void readFilter(boolean readFilterEnabled) throws IOException { + if (this.readFilterEnabled != readFilterEnabled) { + this.readFilterEnabled = readFilterEnabled; + evSet(Native.EVFILT_READ, readFilterEnabled ? Native.EV_ADD_CLEAR_ENABLE : Native.EV_DELETE_DISABLE); + } + } + + void writeFilter(boolean writeFilterEnabled) throws IOException { + if (this.writeFilterEnabled != writeFilterEnabled) { + this.writeFilterEnabled = writeFilterEnabled; + evSet(Native.EVFILT_WRITE, writeFilterEnabled ? Native.EV_ADD_CLEAR_ENABLE : Native.EV_DELETE_DISABLE); + } + } + + private void evSet(short filter, short flags) { + if (isOpen() && isRegistered()) { + evSet0(filter, flags); + } + } + + private void evSet0(short filter, short flags) { + ((KQueueEventLoop) eventLoop()).evSet(this, filter, flags, 0); + } + + abstract class AbstractKQueueUnsafe extends AbstractUnsafe { + boolean readPending; + boolean maybeMoreDataToRead; + private KQueueRecvByteAllocatorHandle allocHandle; + private final Runnable readReadyRunnable = new Runnable() { + @Override + public void run() { + readReadyRunnablePending = false; + readReady(recvBufAllocHandle()); + } + }; + + final void readReady(long numberBytesPending) { + KQueueRecvByteAllocatorHandle allocHandle = recvBufAllocHandle(); + allocHandle.numberBytesPending(numberBytesPending); + readReady(allocHandle); + } + + abstract void readReady(KQueueRecvByteAllocatorHandle allocHandle); + + final void readReadyBefore() { maybeMoreDataToRead = false; } + + final void readReadyFinally(ChannelConfig config) { + maybeMoreDataToRead = allocHandle.maybeMoreDataToRead(); + // Check if there is a readPending which was not processed yet. + // This could be for two reasons: + // * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method + // * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method + // + // See https://github.com/netty/netty/issues/2254 + if (!readPending && !config.isAutoRead()) { + clearReadFilter0(); + } else if (readPending && maybeMoreDataToRead) { + // trigger a read again as there may be something left to read and because of ET we + // will not get notified again until we read everything from the socket + // + // It is possible the last fireChannelRead call could cause the user to call read() again, or if + // autoRead is true the call to channelReadComplete would also call read, but maybeMoreDataToRead is set + // to false before every read operation to prevent re-entry into readReady() we will not read from + // the underlying OS again unless the user happens to call read again. + executeReadReadyRunnable(config); + } + } + + void writeReady() { + if (socket.isOutputShutdown()) { + return; + } + // directly call super.flush0() to force a flush now + super.flush0(); + } + + /** + * Shutdown the input side of the channel. + */ + void shutdownInput(boolean readEOF) { + if (!socket.isInputShutdown()) { + if (isAllowHalfClosure(config())) { + try { + socket.shutdown(true, false); + } catch (IOException ignored) { + // We attempted to shutdown and failed, which means the input has already effectively been + // shutdown. + fireEventAndClose(ChannelInputShutdownEvent.INSTANCE); + return; + } catch (NotYetConnectedException ignore) { + // We attempted to shutdown and failed, which means the input has already effectively been + // shutdown. + } + pipeline().fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE); + } else { + close(voidPromise()); + } + } else if (!readEOF) { + inputClosedSeenErrorOnRead = true; + pipeline().fireUserEventTriggered(ChannelInputShutdownReadComplete.INSTANCE); + } + } + + final void readEOF() { + // This must happen before we attempt to read. This will ensure reading continues until an error occurs. + final KQueueRecvByteAllocatorHandle allocHandle = recvBufAllocHandle(); + allocHandle.readEOF(); + + if (isActive()) { + // If it is still active, we need to call readReady as otherwise we may miss to + // read pending data from the underlying file descriptor. + // See https://github.com/netty/netty/issues/3709 + readReady(allocHandle); + } else { + // Just to be safe make sure the input marked as closed. + shutdownInput(true); + } + } + + @Override + public KQueueRecvByteAllocatorHandle recvBufAllocHandle() { + if (allocHandle == null) { + allocHandle = new KQueueRecvByteAllocatorHandle( + (RecvByteBufAllocator.ExtendedHandle) super.recvBufAllocHandle()); + } + return allocHandle; + } + + final void executeReadReadyRunnable(ChannelConfig config) { + if (readReadyRunnablePending || !isActive() || shouldBreakReadReady(config)) { + return; + } + readReadyRunnablePending = true; + eventLoop().execute(readReadyRunnable); + } + + protected final void clearReadFilter0() { + assert eventLoop().inEventLoop(); + try { + readPending = false; + readFilter(false); + } catch (IOException e) { + // When this happens there is something completely wrong with either the filedescriptor or epoll, + // so fire the exception through the pipeline and close the Channel. + pipeline().fireExceptionCaught(e); + unsafe().close(unsafe().voidPromise()); + } + } + + private void fireEventAndClose(Object evt) { + pipeline().fireUserEventTriggered(evt); + close(voidPromise()); + } + } +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/AbstractKQueueServerChannel.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/AbstractKQueueServerChannel.java new file mode 100644 index 0000000000..1f582c63f9 --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/AbstractKQueueServerChannel.java @@ -0,0 +1,127 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelConfig; +import io.netty.channel.ChannelMetadata; +import io.netty.channel.ChannelOutboundBuffer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.ChannelPromise; +import io.netty.channel.EventLoop; +import io.netty.channel.ServerChannel; +import io.netty.util.internal.UnstableApi; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +@UnstableApi +public abstract class AbstractKQueueServerChannel extends AbstractKQueueChannel implements ServerChannel { + private static final ChannelMetadata METADATA = new ChannelMetadata(false, 16); + + AbstractKQueueServerChannel(BsdSocket fd, boolean active) { + super(null, fd, active); + } + + @Override + public ChannelMetadata metadata() { + return METADATA; + } + + @Override + protected boolean isCompatible(EventLoop loop) { + return loop instanceof KQueueEventLoop; + } + + @Override + protected InetSocketAddress remoteAddress0() { + return null; + } + + @Override + protected AbstractKQueueUnsafe newUnsafe() { + return new KQueueServerSocketUnsafe(); + } + + @Override + protected void doWrite(ChannelOutboundBuffer in) throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + protected Object filterOutboundMessage(Object msg) throws Exception { + throw new UnsupportedOperationException(); + } + + abstract Channel newChildChannel(int fd, byte[] remote, int offset, int len) throws Exception; + + final class KQueueServerSocketUnsafe extends AbstractKQueueUnsafe { + // Will hold the remote address after accept(...) was successful. + // We need 24 bytes for the address as maximum + 1 byte for storing the capacity. + // So use 26 bytes as it's a power of two. + private final byte[] acceptedAddress = new byte[26]; + + @Override + public void connect(SocketAddress socketAddress, SocketAddress socketAddress2, ChannelPromise channelPromise) { + // Connect not supported by ServerChannel implementations + channelPromise.setFailure(new UnsupportedOperationException()); + } + + @Override + void readReady(KQueueRecvByteAllocatorHandle allocHandle) { + assert eventLoop().inEventLoop(); + final ChannelConfig config = config(); + if (shouldBreakReadReady(config)) { + clearReadFilter0(); + return; + } + final ChannelPipeline pipeline = pipeline(); + allocHandle.reset(config); + allocHandle.attemptedBytesRead(1); + readReadyBefore(); + + Throwable exception = null; + try { + try { + do { + int acceptFd = socket.accept(acceptedAddress); + if (acceptFd == -1) { + // this means everything was handled for now + allocHandle.lastBytesRead(-1); + break; + } + allocHandle.lastBytesRead(1); + allocHandle.incMessagesRead(1); + + readPending = false; + pipeline.fireChannelRead(newChildChannel(acceptFd, acceptedAddress, 1, + acceptedAddress[0])); + } while (allocHandle.continueReading()); + } catch (Throwable t) { + exception = t; + } + allocHandle.readComplete(); + pipeline.fireChannelReadComplete(); + + if (exception != null) { + pipeline.fireExceptionCaught(exception); + } + } finally { + readReadyFinally(config); + } + } + } +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/AbstractKQueueStreamChannel.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/AbstractKQueueStreamChannel.java new file mode 100644 index 0000000000..75a02f86f5 --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/AbstractKQueueStreamChannel.java @@ -0,0 +1,808 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.CompositeByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelConfig; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelMetadata; +import io.netty.channel.ChannelOutboundBuffer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.ChannelPromise; +import io.netty.channel.ConnectTimeoutException; +import io.netty.channel.DefaultFileRegion; +import io.netty.channel.EventLoop; +import io.netty.channel.FileRegion; +import io.netty.channel.socket.DuplexChannel; +import io.netty.channel.unix.IovArray; +import io.netty.channel.unix.SocketWritableByteChannel; +import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.StringUtil; +import io.netty.util.internal.ThrowableUtil; +import io.netty.util.internal.UnstableApi; + +import java.io.IOException; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.ConnectionPendingException; +import java.nio.channels.WritableByteChannel; +import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import static io.netty.channel.unix.Limits.IOV_MAX; + +@UnstableApi +public abstract class AbstractKQueueStreamChannel extends AbstractKQueueChannel implements DuplexChannel { + private static final ChannelMetadata METADATA = new ChannelMetadata(false, 16); + private static final ClosedChannelException DO_CLOSE_CLOSED_CHANNEL_EXCEPTION = ThrowableUtil.unknownStackTrace( + new ClosedChannelException(), AbstractKQueueStreamChannel.class, "doClose()"); + private static final String EXPECTED_TYPES = + " (expected: " + StringUtil.simpleClassName(ByteBuf.class) + ", " + + StringUtil.simpleClassName(DefaultFileRegion.class) + ')'; + + private ChannelPromise connectPromise; + private ScheduledFuture connectTimeoutFuture; + private SocketAddress requestedRemoteAddress; + private WritableByteChannel byteChannel; + + AbstractKQueueStreamChannel(Channel parent, BsdSocket fd, boolean active) { + super(parent, fd, active, true); + } + + @Override + protected AbstractKQueueUnsafe newUnsafe() { + return new KQueueStreamUnsafe(); + } + + @Override + public ChannelMetadata metadata() { + return METADATA; + } + + /** + * Write bytes form the given {@link ByteBuf} to the underlying {@link java.nio.channels.Channel}. + * @param buf the {@link ByteBuf} from which the bytes should be written + */ + private boolean writeBytes(ChannelOutboundBuffer in, ByteBuf buf, int writeSpinCount) throws Exception { + int readableBytes = buf.readableBytes(); + if (readableBytes == 0) { + in.remove(); + return true; + } + + if (buf.hasMemoryAddress() || buf.nioBufferCount() == 1) { + int writtenBytes = doWriteBytes(buf, writeSpinCount); + in.removeBytes(writtenBytes); + return writtenBytes == readableBytes; + } else { + ByteBuffer[] nioBuffers = buf.nioBuffers(); + return writeBytesMultiple(in, nioBuffers, nioBuffers.length, readableBytes, writeSpinCount); + } + } + + private boolean writeBytesMultiple( + ChannelOutboundBuffer in, IovArray array, int writeSpinCount) throws IOException { + + long expectedWrittenBytes = array.size(); + final long initialExpectedWrittenBytes = expectedWrittenBytes; + + int cnt = array.count(); + + assert expectedWrittenBytes != 0; + assert cnt != 0; + + boolean done = false; + int offset = 0; + int end = offset + cnt; + for (int i = writeSpinCount; i > 0; --i) { + long localWrittenBytes = socket.writevAddresses(array.memoryAddress(offset), cnt); + if (localWrittenBytes == 0) { + break; + } + expectedWrittenBytes -= localWrittenBytes; + + if (expectedWrittenBytes == 0) { + // Written everything, just break out here (fast-path) + done = true; + break; + } + + do { + long bytes = array.processWritten(offset, localWrittenBytes); + if (bytes == -1) { + // incomplete write + break; + } else { + offset++; + cnt--; + localWrittenBytes -= bytes; + } + } while (offset < end && localWrittenBytes > 0); + } + in.removeBytes(initialExpectedWrittenBytes - expectedWrittenBytes); + return done; + } + + private boolean writeBytesMultiple( + ChannelOutboundBuffer in, ByteBuffer[] nioBuffers, + int nioBufferCnt, long expectedWrittenBytes, int writeSpinCount) throws IOException { + + assert expectedWrittenBytes != 0; + final long initialExpectedWrittenBytes = expectedWrittenBytes; + + boolean done = false; + int offset = 0; + int end = offset + nioBufferCnt; + for (int i = writeSpinCount; i > 0; --i) { + long localWrittenBytes = socket.writev(nioBuffers, offset, nioBufferCnt); + if (localWrittenBytes == 0) { + break; + } + expectedWrittenBytes -= localWrittenBytes; + + if (expectedWrittenBytes == 0) { + // Written everything, just break out here (fast-path) + done = true; + break; + } + do { + ByteBuffer buffer = nioBuffers[offset]; + int pos = buffer.position(); + int bytes = buffer.limit() - pos; + if (bytes > localWrittenBytes) { + buffer.position(pos + (int) localWrittenBytes); + // incomplete write + break; + } else { + offset++; + nioBufferCnt--; + localWrittenBytes -= bytes; + } + } while (offset < end && localWrittenBytes > 0); + } + + in.removeBytes(initialExpectedWrittenBytes - expectedWrittenBytes); + return done; + } + + /** + * Write a {@link DefaultFileRegion} + * + * @param region the {@link DefaultFileRegion} from which the bytes should be written + * @return amount the amount of written bytes + */ + private boolean writeDefaultFileRegion( + ChannelOutboundBuffer in, DefaultFileRegion region, int writeSpinCount) throws Exception { + final long regionCount = region.count(); + if (region.transferred() >= regionCount) { + in.remove(); + return true; + } + + final long baseOffset = region.position(); + boolean done = false; + long flushedAmount = 0; + + for (int i = writeSpinCount; i > 0; --i) { + final long offset = region.transferred(); + final long localFlushedAmount = socket.sendFile(region, baseOffset, offset, regionCount - offset); + if (localFlushedAmount == 0) { + break; + } + + flushedAmount += localFlushedAmount; + if (region.transferred() >= regionCount) { + done = true; + break; + } + } + + if (flushedAmount > 0) { + in.progress(flushedAmount); + } + + if (done) { + in.remove(); + } + return done; + } + + private boolean writeFileRegion( + ChannelOutboundBuffer in, FileRegion region, final int writeSpinCount) throws Exception { + if (region.transferred() >= region.count()) { + in.remove(); + return true; + } + + boolean done = false; + long flushedAmount = 0; + + if (byteChannel == null) { + byteChannel = new KQueueSocketWritableByteChannel(); + } + for (int i = writeSpinCount; i > 0; --i) { + final long localFlushedAmount = region.transferTo(byteChannel, region.transferred()); + if (localFlushedAmount == 0) { + break; + } + + flushedAmount += localFlushedAmount; + if (region.transferred() >= region.count()) { + done = true; + break; + } + } + + if (flushedAmount > 0) { + in.progress(flushedAmount); + } + + if (done) { + in.remove(); + } + return done; + } + + @Override + protected void doWrite(ChannelOutboundBuffer in) throws Exception { + int writeSpinCount = config().getWriteSpinCount(); + for (;;) { + final int msgCount = in.size(); + + if (msgCount == 0) { + // Wrote all messages. + writeFilter(false); + // Return here so we not set the EPOLLOUT flag. + return; + } + + // Do gathering write if the outbounf buffer entries start with more than one ByteBuf. + if (msgCount > 1 && in.current() instanceof ByteBuf) { + if (!doWriteMultiple(in, writeSpinCount)) { + // Break the loop and so set EPOLLOUT flag. + break; + } + + // We do not break the loop here even if the outbound buffer was flushed completely, + // because a user might have triggered another write and flush when we notify his or her + // listeners. + } else { // msgCount == 1 + if (!doWriteSingle(in, writeSpinCount)) { + // Break the loop and so set EPOLLOUT flag. + break; + } + } + } + // Underlying descriptor can not accept all data currently, so set the EPOLLOUT flag to be woken up + // when it can accept more data. + writeFilter(true); + } + + protected boolean doWriteSingle(ChannelOutboundBuffer in, int writeSpinCount) throws Exception { + // The outbound buffer contains only one message or it contains a file region. + Object msg = in.current(); + if (msg instanceof ByteBuf) { + if (!writeBytes(in, (ByteBuf) msg, writeSpinCount)) { + // was not able to write everything so break here we will get notified later again once + // the network stack can handle more writes. + return false; + } + } else if (msg instanceof DefaultFileRegion) { + if (!writeDefaultFileRegion(in, (DefaultFileRegion) msg, writeSpinCount)) { + // was not able to write everything so break here we will get notified later again once + // the network stack can handle more writes. + return false; + } + } else if (msg instanceof FileRegion) { + if (!writeFileRegion(in, (FileRegion) msg, writeSpinCount)) { + // was not able to write everything so break here we will get notified later again once + // the network stack can handle more writes. + return false; + } + } else { + // Should never reach here. + throw new Error(); + } + + return true; + } + + private boolean doWriteMultiple(ChannelOutboundBuffer in, int writeSpinCount) throws Exception { + if (PlatformDependent.hasUnsafe()) { + // this means we can cast to IovArray and write the IovArray directly. + IovArray array = ((KQueueEventLoop) eventLoop()).cleanArray(); + in.forEachFlushedMessage(array); + + int cnt = array.count(); + if (cnt >= 1) { + // TODO: Handle the case where cnt == 1 specially. + if (!writeBytesMultiple(in, array, writeSpinCount)) { + // was not able to write everything so break here we will get notified later again once + // the network stack can handle more writes. + return false; + } + } else { // cnt == 0, which means the outbound buffer contained empty buffers only. + in.removeBytes(0); + } + } else { + ByteBuffer[] buffers = in.nioBuffers(); + int cnt = in.nioBufferCount(); + if (cnt >= 1) { + // TODO: Handle the case where cnt == 1 specially. + if (!writeBytesMultiple(in, buffers, cnt, in.nioBufferSize(), writeSpinCount)) { + // was not able to write everything so break here we will get notified later again once + // the network stack can handle more writes. + return false; + } + } else { // cnt == 0, which means the outbound buffer contained empty buffers only. + in.removeBytes(0); + } + } + + return true; + } + + @Override + protected Object filterOutboundMessage(Object msg) { + if (msg instanceof ByteBuf) { + ByteBuf buf = (ByteBuf) msg; + if (!buf.hasMemoryAddress() && (PlatformDependent.hasUnsafe() || !buf.isDirect())) { + if (buf instanceof CompositeByteBuf) { + // Special handling of CompositeByteBuf to reduce memory copies if some of the Components + // in the CompositeByteBuf are backed by a memoryAddress. + CompositeByteBuf comp = (CompositeByteBuf) buf; + if (!comp.isDirect() || comp.nioBufferCount() > IOV_MAX) { + // more then 1024 buffers for gathering writes so just do a memory copy. + buf = newDirectBuffer(buf); + assert buf.hasMemoryAddress(); + } + } else { + // We can only handle buffers with memory address so we need to copy if a non direct is + // passed to write. + buf = newDirectBuffer(buf); + assert buf.hasMemoryAddress(); + } + } + return buf; + } + + if (msg instanceof FileRegion) { + return msg; + } + + throw new UnsupportedOperationException( + "unsupported message type: " + StringUtil.simpleClassName(msg) + EXPECTED_TYPES); + } + + private void shutdownOutput0(final ChannelPromise promise) { + try { + socket.shutdown(false, true); + promise.setSuccess(); + } catch (Throwable cause) { + promise.setFailure(cause); + } + } + + private void shutdownInput0(final ChannelPromise promise) { + try { + socket.shutdown(true, false); + promise.setSuccess(); + } catch (Throwable cause) { + promise.setFailure(cause); + } + } + + private void shutdown0(final ChannelPromise promise) { + try { + socket.shutdown(true, true); + promise.setSuccess(); + } catch (Throwable cause) { + promise.setFailure(cause); + } + } + + @Override + public boolean isOutputShutdown() { + return socket.isOutputShutdown(); + } + + @Override + public boolean isInputShutdown() { + return socket.isInputShutdown(); + } + + @Override + public boolean isShutdown() { + return socket.isShutdown(); + } + + @Override + public ChannelFuture shutdownOutput() { + return shutdownOutput(newPromise()); + } + + @Override + public ChannelFuture shutdownOutput(final ChannelPromise promise) { + Executor closeExecutor = ((KQueueStreamUnsafe) unsafe()).prepareToClose(); + if (closeExecutor != null) { + closeExecutor.execute(new Runnable() { + @Override + public void run() { + shutdownOutput0(promise); + } + }); + } else { + EventLoop loop = eventLoop(); + if (loop.inEventLoop()) { + shutdownOutput0(promise); + } else { + loop.execute(new Runnable() { + @Override + public void run() { + shutdownOutput0(promise); + } + }); + } + } + return promise; + } + + @Override + public ChannelFuture shutdownInput() { + return shutdownInput(newPromise()); + } + + @Override + public ChannelFuture shutdownInput(final ChannelPromise promise) { + Executor closeExecutor = ((KQueueStreamUnsafe) unsafe()).prepareToClose(); + if (closeExecutor != null) { + closeExecutor.execute(new Runnable() { + @Override + public void run() { + shutdownInput0(promise); + } + }); + } else { + EventLoop loop = eventLoop(); + if (loop.inEventLoop()) { + shutdownInput0(promise); + } else { + loop.execute(new Runnable() { + @Override + public void run() { + shutdownInput0(promise); + } + }); + } + } + return promise; + } + + @Override + public ChannelFuture shutdown() { + return shutdown(newPromise()); + } + + @Override + public ChannelFuture shutdown(final ChannelPromise promise) { + Executor closeExecutor = ((KQueueStreamUnsafe) unsafe()).prepareToClose(); + if (closeExecutor != null) { + closeExecutor.execute(new Runnable() { + @Override + public void run() { + shutdown0(promise); + } + }); + } else { + EventLoop loop = eventLoop(); + if (loop.inEventLoop()) { + shutdown0(promise); + } else { + loop.execute(new Runnable() { + @Override + public void run() { + shutdown0(promise); + } + }); + } + } + return promise; + } + + @Override + protected void doClose() throws Exception { + ChannelPromise promise = connectPromise; + if (promise != null) { + // Use tryFailure() instead of setFailure() to avoid the race against cancel(). + promise.tryFailure(DO_CLOSE_CLOSED_CHANNEL_EXCEPTION); + connectPromise = null; + } + + ScheduledFuture future = connectTimeoutFuture; + if (future != null) { + future.cancel(false); + connectTimeoutFuture = null; + } + // Calling super.doClose() first so splceTo(...) will fail on next call. + super.doClose(); + } + + /** + * Connect to the remote peer + */ + protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception { + if (localAddress != null) { + socket.bind(localAddress); + } + + boolean success = false; + try { + boolean connected = socket.connect(remoteAddress); + if (!connected) { + writeFilter(true); + } + success = true; + return connected; + } finally { + if (!success) { + doClose(); + } + } + } + + class KQueueStreamUnsafe extends AbstractKQueueUnsafe { + // Overridden here just to be able to access this method from AbstractKQueueStreamChannel + @Override + protected Executor prepareToClose() { + return super.prepareToClose(); + } + + @Override + void readReady(final KQueueRecvByteAllocatorHandle allocHandle) { + final ChannelConfig config = config(); + if (shouldBreakReadReady(config)) { + clearReadFilter0(); + return; + } + final ChannelPipeline pipeline = pipeline(); + final ByteBufAllocator allocator = config.getAllocator(); + allocHandle.reset(config); + readReadyBefore(); + + ByteBuf byteBuf = null; + boolean close = false; + try { + do { + // we use a direct buffer here as the native implementations only be able + // to handle direct buffers. + byteBuf = allocHandle.allocate(allocator); + allocHandle.lastBytesRead(doReadBytes(byteBuf)); + if (allocHandle.lastBytesRead() <= 0) { + // nothing was read, release the buffer. + byteBuf.release(); + byteBuf = null; + close = allocHandle.lastBytesRead() < 0; + break; + } + allocHandle.incMessagesRead(1); + readPending = false; + pipeline.fireChannelRead(byteBuf); + byteBuf = null; + + if (shouldBreakReadReady(config)) { + // We need to do this for two reasons: + // + // - If the input was shutdown in between (which may be the case when the user did it in the + // fireChannelRead(...) method we should not try to read again to not produce any + // miss-leading exceptions. + // + // - If the user closes the channel we need to ensure we not try to read from it again as + // the filedescriptor may be re-used already by the OS if the system is handling a lot of + // concurrent connections and so needs a lot of filedescriptors. If not do this we risk + // reading data from a filedescriptor that belongs to another socket then the socket that + // was "wrapped" by this Channel implementation. + break; + } + } while (allocHandle.continueReading()); + + allocHandle.readComplete(); + pipeline.fireChannelReadComplete(); + + if (close) { + shutdownInput(false); + } + } catch (Throwable t) { + handleReadException(pipeline, byteBuf, t, close, allocHandle); + } finally { + readReadyFinally(config); + } + } + + @Override + void writeReady() { + if (connectPromise != null) { + // pending connect which is now complete so handle it. + finishConnect(); + } else { + super.writeReady(); + } + } + + @Override + public void connect( + final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) { + if (!promise.setUncancellable() || !ensureOpen(promise)) { + return; + } + + try { + if (connectPromise != null) { + throw new ConnectionPendingException(); + } + + boolean wasActive = isActive(); + if (doConnect(remoteAddress, localAddress)) { + fulfillConnectPromise(promise, wasActive); + } else { + connectPromise = promise; + requestedRemoteAddress = remoteAddress; + + // Schedule connect timeout. + int connectTimeoutMillis = config().getConnectTimeoutMillis(); + if (connectTimeoutMillis > 0) { + connectTimeoutFuture = eventLoop().schedule(new Runnable() { + @Override + public void run() { + ChannelPromise connectPromise = AbstractKQueueStreamChannel.this.connectPromise; + ConnectTimeoutException cause = + new ConnectTimeoutException("connection timed out: " + remoteAddress); + if (connectPromise != null && connectPromise.tryFailure(cause)) { + close(voidPromise()); + } + } + }, connectTimeoutMillis, TimeUnit.MILLISECONDS); + } + + promise.addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (future.isCancelled()) { + if (connectTimeoutFuture != null) { + connectTimeoutFuture.cancel(false); + } + connectPromise = null; + close(voidPromise()); + } + } + }); + } + } catch (Throwable t) { + closeIfClosed(); + promise.tryFailure(annotateConnectException(t, remoteAddress)); + } + } + + private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) { + if (promise == null) { + // Closed via cancellation and the promise has been notified already. + return; + } + active = true; + + // Get the state as trySuccess() may trigger an ChannelFutureListener that will close the Channel. + // We still need to ensure we call fireChannelActive() in this case. + boolean active = isActive(); + + // trySuccess() will return false if a user cancelled the connection attempt. + boolean promiseSet = promise.trySuccess(); + + // Regardless if the connection attempt was cancelled, channelActive() event should be triggered, + // because what happened is what happened. + if (!wasActive && active) { + pipeline().fireChannelActive(); + } + + // If a user cancelled the connection attempt, close the channel, which is followed by channelInactive(). + if (!promiseSet) { + close(voidPromise()); + } + } + + private void fulfillConnectPromise(ChannelPromise promise, Throwable cause) { + if (promise == null) { + // Closed via cancellation and the promise has been notified already. + return; + } + + // Use tryFailure() instead of setFailure() to avoid the race against cancel(). + promise.tryFailure(cause); + closeIfClosed(); + } + + private void finishConnect() { + // Note this method is invoked by the event loop only if the connection attempt was + // neither cancelled nor timed out. + + assert eventLoop().inEventLoop(); + + boolean connectStillInProgress = false; + try { + boolean wasActive = isActive(); + if (!doFinishConnect()) { + connectStillInProgress = true; + return; + } + fulfillConnectPromise(connectPromise, wasActive); + } catch (Throwable t) { + fulfillConnectPromise(connectPromise, annotateConnectException(t, requestedRemoteAddress)); + } finally { + if (!connectStillInProgress) { + // Check for null as the connectTimeoutFuture is only created if a connectTimeoutMillis > 0 is used + // See https://github.com/netty/netty/issues/1770 + if (connectTimeoutFuture != null) { + connectTimeoutFuture.cancel(false); + } + connectPromise = null; + } + } + } + + boolean doFinishConnect() throws Exception { + if (socket.finishConnect()) { + writeFilter(false); + return true; + } else { + writeFilter(true); + return false; + } + } + + private void handleReadException(ChannelPipeline pipeline, ByteBuf byteBuf, Throwable cause, boolean close, + KQueueRecvByteAllocatorHandle allocHandle) { + if (byteBuf != null) { + if (byteBuf.isReadable()) { + readPending = false; + pipeline.fireChannelRead(byteBuf); + } else { + byteBuf.release(); + } + } + allocHandle.readComplete(); + pipeline.fireChannelReadComplete(); + pipeline.fireExceptionCaught(cause); + if (close || cause instanceof IOException) { + shutdownInput(false); + } + } + } + + private final class KQueueSocketWritableByteChannel extends SocketWritableByteChannel { + KQueueSocketWritableByteChannel() { + super(socket); + } + + @Override + protected ByteBufAllocator alloc() { + return AbstractKQueueStreamChannel.this.alloc(); + } + } +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/AcceptFilter.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/AcceptFilter.java new file mode 100644 index 0000000000..5c7b13f2ac --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/AcceptFilter.java @@ -0,0 +1,61 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.util.internal.ObjectUtil; +import io.netty.util.internal.UnstableApi; + +@UnstableApi +public final class AcceptFilter { + static final AcceptFilter PLATFORM_UNSUPPORTED = new AcceptFilter("", ""); + private final String filterName; + private final String filterArgs; + + public AcceptFilter(String filterName, String filterArgs) { + this.filterName = ObjectUtil.checkNotNull(filterName, "filterName"); + this.filterArgs = ObjectUtil.checkNotNull(filterArgs, "filterArgs"); + } + + public String filterName() { + return filterName; + } + + public String filterArgs() { + return filterArgs; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof AcceptFilter)) { + return false; + } + AcceptFilter rhs = (AcceptFilter) o; + return filterName.equals(rhs.filterName) && filterArgs.equals(rhs.filterArgs); + } + + @Override + public int hashCode() { + return 31 * (31 + filterName.hashCode()) + filterArgs.hashCode(); + } + + @Override + public String toString() { + return filterName + ", " + filterArgs; + } +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/BsdSocket.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/BsdSocket.java new file mode 100644 index 0000000000..c8e8ecd42a --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/BsdSocket.java @@ -0,0 +1,119 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.channel.DefaultFileRegion; +import io.netty.channel.unix.Errors; +import io.netty.channel.unix.PeerCredentials; +import io.netty.channel.unix.Socket; +import io.netty.util.internal.ThrowableUtil; + +import java.io.IOException; +import java.nio.channels.ClosedChannelException; + +import static io.netty.channel.kqueue.AcceptFilter.PLATFORM_UNSUPPORTED; +import static io.netty.channel.unix.Errors.ERRNO_EPIPE_NEGATIVE; +import static io.netty.channel.unix.Errors.ioResult; +import static io.netty.channel.unix.Errors.newConnectionResetException; + +/** + * A socket which provides access BSD native methods. + */ +final class BsdSocket extends Socket { + private static final Errors.NativeIoException SENDFILE_CONNECTION_RESET_EXCEPTION; + private static final ClosedChannelException SENDFILE_CLOSED_CHANNEL_EXCEPTION = ThrowableUtil.unknownStackTrace( + new ClosedChannelException(), Native.class, "sendfile(..)"); + + // These limits are just based on observations. I couldn't find anything in header files which formally + // define these limits. + private static final int APPLE_SND_LOW_AT_MAX = 1 << 17; + private static final int FREEBSD_SND_LOW_AT_MAX = 1 << 15; + static final int BSD_SND_LOW_AT_MAX = Math.min(APPLE_SND_LOW_AT_MAX, FREEBSD_SND_LOW_AT_MAX); + + static { + SENDFILE_CONNECTION_RESET_EXCEPTION = newConnectionResetException("syscall:sendfile", + ERRNO_EPIPE_NEGATIVE); + } + + BsdSocket(int fd) { + super(fd); + } + + void setAcceptFilter(AcceptFilter acceptFilter) throws IOException { + setAcceptFilter(intValue(), acceptFilter.filterName(), acceptFilter.filterArgs()); + } + + void setTcpNoPush(boolean tcpNoPush) throws IOException { + setTcpNoPush(intValue(), tcpNoPush ? 1 : 0); + } + + void setSndLowAt(int lowAt) throws IOException { + setSndLowAt(intValue(), lowAt); + } + + boolean isTcpNoPush() throws IOException { + return getTcpNoPush(intValue()) != 0; + } + + int getSndLowAt() throws IOException { + return getSndLowAt(intValue()); + } + + AcceptFilter getAcceptFilter() throws IOException { + String[] result = getAcceptFilter(intValue()); + return result == null ? PLATFORM_UNSUPPORTED : new AcceptFilter(result[0], result[1]); + } + + PeerCredentials getPeerCredentials() throws IOException { + return getPeerCredentials(intValue()); + } + + long sendFile(DefaultFileRegion src, long baseOffset, long offset, long length) throws IOException { + // Open the file-region as it may be created via the lazy constructor. This is needed as we directly access + // the FileChannel field directly via JNI + src.open(); + + long res = sendFile(intValue(), src, baseOffset, offset, length); + if (res >= 0) { + return res; + } + return ioResult("sendfile", (int) res, SENDFILE_CONNECTION_RESET_EXCEPTION, SENDFILE_CLOSED_CHANNEL_EXCEPTION); + } + + public static BsdSocket newSocketStream() { + return new BsdSocket(newSocketStream0()); + } + + public static BsdSocket newSocketDgram() { + return new BsdSocket(newSocketDgram0()); + } + + public static BsdSocket newSocketDomain() { + return new BsdSocket(newSocketDomain0()); + } + + private static native long sendFile(int socketFd, DefaultFileRegion src, long baseOffset, + long offset, long length) throws IOException; + + private static native String[] getAcceptFilter(int fd) throws IOException; + private static native int getTcpNoPush(int fd) throws IOException; + private static native int getSndLowAt(int fd) throws IOException; + private static native PeerCredentials getPeerCredentials(int fd) throws IOException; + + private static native void setAcceptFilter(int fd, String filterName, String filterArgs) throws IOException; + private static native void setTcpNoPush(int fd, int tcpNoPush) throws IOException; + private static native void setSndLowAt(int fd, int lowAt) throws IOException; +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueue.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueue.java new file mode 100644 index 0000000000..3377d9fdac --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueue.java @@ -0,0 +1,86 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.channel.unix.FileDescriptor; +import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.UnstableApi; + +/** + * If KQueue is available the JNI resources will be loaded when this class loads. + */ +@UnstableApi +public final class KQueue { + private static final Throwable UNAVAILABILITY_CAUSE; + + static { + Throwable cause = null; + FileDescriptor kqueueFd = null; + try { + kqueueFd = Native.newKQueue(); + } catch (Throwable t) { + cause = t; + } finally { + if (kqueueFd != null) { + try { + kqueueFd.close(); + } catch (Exception ignore) { + // ignore + } + } + } + + if (cause != null) { + UNAVAILABILITY_CAUSE = cause; + } else { + UNAVAILABILITY_CAUSE = PlatformDependent.hasUnsafe() ? null : + new IllegalStateException("sun.misc.Unsafe not available"); + } + } + + /** + * Returns {@code true} if and only if the + * {@code netty-transport-native-kqueue} is available. + */ + public static boolean isAvailable() { + return UNAVAILABILITY_CAUSE == null; + } + + /** + * Ensure that {@code netty-transport-native-kqueue} is + * available. + * + * @throws UnsatisfiedLinkError if unavailable + */ + public static void ensureAvailability() { + if (UNAVAILABILITY_CAUSE != null) { + throw (Error) new UnsatisfiedLinkError( + "failed to load the required native library").initCause(UNAVAILABILITY_CAUSE); + } + } + + /** + * Returns the cause of unavailability of + * {@code netty-transport-native-kqueue}. + * + * @return the cause if unavailable. {@code null} if available. + */ + public static Throwable unavailabilityCause() { + return UNAVAILABILITY_CAUSE; + } + + private KQueue() { } +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueChannelConfig.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueChannelConfig.java new file mode 100644 index 0000000000..7c477aa2bb --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueChannelConfig.java @@ -0,0 +1,156 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.ChannelOption; +import io.netty.channel.DefaultChannelConfig; +import io.netty.channel.MessageSizeEstimator; +import io.netty.channel.RecvByteBufAllocator; +import io.netty.channel.WriteBufferWaterMark; +import io.netty.util.internal.UnstableApi; + +import java.util.Map; + +import static io.netty.channel.kqueue.KQueueChannelOption.RCV_ALLOC_TRANSPORT_PROVIDES_GUESS; + +@UnstableApi +public class KQueueChannelConfig extends DefaultChannelConfig { + final AbstractKQueueChannel channel; + private volatile boolean transportProvidesGuess; + + KQueueChannelConfig(AbstractKQueueChannel channel) { + super(channel); + this.channel = channel; + } + + @Override + @SuppressWarnings("deprecation") + public Map, Object> getOptions() { + return getOptions(super.getOptions(), RCV_ALLOC_TRANSPORT_PROVIDES_GUESS); + } + + @SuppressWarnings("unchecked") + @Override + public T getOption(ChannelOption option) { + if (option == RCV_ALLOC_TRANSPORT_PROVIDES_GUESS) { + return (T) Boolean.valueOf(getRcvAllocTransportProvidesGuess()); + } + return super.getOption(option); + } + + @Override + public boolean setOption(ChannelOption option, T value) { + validate(option, value); + + if (option == RCV_ALLOC_TRANSPORT_PROVIDES_GUESS) { + setRcvAllocTransportProvidesGuess((Boolean) value); + } else { + return super.setOption(option, value); + } + + return true; + } + + /** + * If this is {@code true} then the {@link RecvByteBufAllocator.Handle#guess()} will be overriden to always attempt + * to read as many bytes as kqueue says are available. + */ + public KQueueChannelConfig setRcvAllocTransportProvidesGuess(boolean transportProvidesGuess) { + this.transportProvidesGuess = transportProvidesGuess; + return this; + } + + /** + * If this is {@code true} then the {@link RecvByteBufAllocator.Handle#guess()} will be overriden to always attempt + * to read as many bytes as kqueue says are available. + */ + public boolean getRcvAllocTransportProvidesGuess() { + return transportProvidesGuess; + } + + @Override + public KQueueChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis) { + super.setConnectTimeoutMillis(connectTimeoutMillis); + return this; + } + + @Override + @Deprecated + public KQueueChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead) { + super.setMaxMessagesPerRead(maxMessagesPerRead); + return this; + } + + @Override + public KQueueChannelConfig setWriteSpinCount(int writeSpinCount) { + super.setWriteSpinCount(writeSpinCount); + return this; + } + + @Override + public KQueueChannelConfig setAllocator(ByteBufAllocator allocator) { + super.setAllocator(allocator); + return this; + } + + @Override + public KQueueChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator) { + if (!(allocator.newHandle() instanceof RecvByteBufAllocator.ExtendedHandle)) { + throw new IllegalArgumentException("allocator.newHandle() must return an object of type: " + + RecvByteBufAllocator.ExtendedHandle.class); + } + super.setRecvByteBufAllocator(allocator); + return this; + } + + @Override + public KQueueChannelConfig setAutoRead(boolean autoRead) { + super.setAutoRead(autoRead); + return this; + } + + @Override + @Deprecated + public KQueueChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark) { + super.setWriteBufferHighWaterMark(writeBufferHighWaterMark); + return this; + } + + @Override + @Deprecated + public KQueueChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark) { + super.setWriteBufferLowWaterMark(writeBufferLowWaterMark); + return this; + } + + @Override + public KQueueChannelConfig setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark) { + super.setWriteBufferWaterMark(writeBufferWaterMark); + return this; + } + + @Override + public KQueueChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator) { + super.setMessageSizeEstimator(estimator); + return this; + } + + @Override + protected final void autoReadCleared() { + channel.clearReadFilter(); + } +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueChannelOption.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueChannelOption.java new file mode 100644 index 0000000000..be5dc4e887 --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueChannelOption.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.channel.ChannelOption; +import io.netty.channel.RecvByteBufAllocator; +import io.netty.channel.unix.UnixChannelOption; +import io.netty.util.internal.UnstableApi; + +@UnstableApi +public final class KQueueChannelOption extends UnixChannelOption { + public static final ChannelOption SO_SNDLOWAT = valueOf(KQueueChannelOption.class, "SO_SNDLOWAT"); + public static final ChannelOption TCP_NOPUSH = valueOf(KQueueChannelOption.class, "TCP_NOPUSH"); + public static final ChannelOption SO_ACCEPTFILTER = + valueOf(KQueueChannelOption.class, "SO_ACCEPTFILTER"); + /** + * If this is {@code true} then the {@link RecvByteBufAllocator.Handle#guess()} will be overriden to always attempt + * to read as many bytes as kqueue says are available. + */ + public static final ChannelOption RCV_ALLOC_TRANSPORT_PROVIDES_GUESS = + valueOf(KQueueChannelOption.class, "RCV_ALLOC_TRANSPORT_PROVIDES_GUESS"); + + @SuppressWarnings({ "unused", "deprecation" }) + private KQueueChannelOption() { + } +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueDatagramChannel.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueDatagramChannel.java new file mode 100644 index 0000000000..cffa7f537f --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueDatagramChannel.java @@ -0,0 +1,552 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.CompositeByteBuf; +import io.netty.channel.AddressedEnvelope; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelMetadata; +import io.netty.channel.ChannelOutboundBuffer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.ChannelPromise; +import io.netty.channel.DefaultAddressedEnvelope; +import io.netty.channel.socket.DatagramChannel; +import io.netty.channel.socket.DatagramChannelConfig; +import io.netty.channel.socket.DatagramPacket; +import io.netty.channel.unix.DatagramSocketAddress; +import io.netty.channel.unix.IovArray; +import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.StringUtil; +import io.netty.util.internal.UnstableApi; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.NetworkInterface; +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.ByteBuffer; +import java.nio.channels.NotYetConnectedException; +import java.util.ArrayList; +import java.util.List; + +import static io.netty.channel.kqueue.BsdSocket.newSocketDgram; +import static io.netty.channel.unix.Limits.IOV_MAX; + +@UnstableApi +public final class KQueueDatagramChannel extends AbstractKQueueChannel implements DatagramChannel { + private static final ChannelMetadata METADATA = new ChannelMetadata(true); + private static final String EXPECTED_TYPES = + " (expected: " + StringUtil.simpleClassName(DatagramPacket.class) + ", " + + StringUtil.simpleClassName(AddressedEnvelope.class) + '<' + + StringUtil.simpleClassName(ByteBuf.class) + ", " + + StringUtil.simpleClassName(InetSocketAddress.class) + ">, " + + StringUtil.simpleClassName(ByteBuf.class) + ')'; + + private volatile InetSocketAddress local; + private volatile InetSocketAddress remote; + private volatile boolean connected; + private final KQueueDatagramChannelConfig config; + + public KQueueDatagramChannel() { + super(null, newSocketDgram(), false); + config = new KQueueDatagramChannelConfig(this); + } + + KQueueDatagramChannel(BsdSocket socket, boolean active) { + super(null, socket, active); + config = new KQueueDatagramChannelConfig(this); + // As we create an EpollDatagramChannel from a FileDescriptor we should try to obtain the remote and local + // address from it. This is needed as the FileDescriptor may be bound already. + local = socket.localAddress(); + } + + @Override + public InetSocketAddress remoteAddress() { + return (InetSocketAddress) super.remoteAddress(); + } + + @Override + public InetSocketAddress localAddress() { + return (InetSocketAddress) super.localAddress(); + } + + @Override + public ChannelMetadata metadata() { + return METADATA; + } + + @Override + @SuppressWarnings("deprecation") + public boolean isActive() { + return socket.isOpen() && (config.getActiveOnOpen() && isRegistered() || active); + } + + @Override + public boolean isConnected() { + return connected; + } + + @Override + public ChannelFuture joinGroup(InetAddress multicastAddress) { + return joinGroup(multicastAddress, newPromise()); + } + + @Override + public ChannelFuture joinGroup(InetAddress multicastAddress, ChannelPromise promise) { + try { + return joinGroup( + multicastAddress, + NetworkInterface.getByInetAddress(localAddress().getAddress()), + null, promise); + } catch (SocketException e) { + promise.setFailure(e); + } + return promise; + } + + @Override + public ChannelFuture joinGroup( + InetSocketAddress multicastAddress, NetworkInterface networkInterface) { + return joinGroup(multicastAddress, networkInterface, newPromise()); + } + + @Override + public ChannelFuture joinGroup( + InetSocketAddress multicastAddress, NetworkInterface networkInterface, + ChannelPromise promise) { + return joinGroup(multicastAddress.getAddress(), networkInterface, null, promise); + } + + @Override + public ChannelFuture joinGroup( + InetAddress multicastAddress, NetworkInterface networkInterface, InetAddress source) { + return joinGroup(multicastAddress, networkInterface, source, newPromise()); + } + + @Override + public ChannelFuture joinGroup( + final InetAddress multicastAddress, final NetworkInterface networkInterface, + final InetAddress source, final ChannelPromise promise) { + + if (multicastAddress == null) { + throw new NullPointerException("multicastAddress"); + } + + if (networkInterface == null) { + throw new NullPointerException("networkInterface"); + } + + promise.setFailure(new UnsupportedOperationException("Multicast not supported")); + return promise; + } + + @Override + public ChannelFuture leaveGroup(InetAddress multicastAddress) { + return leaveGroup(multicastAddress, newPromise()); + } + + @Override + public ChannelFuture leaveGroup(InetAddress multicastAddress, ChannelPromise promise) { + try { + return leaveGroup( + multicastAddress, NetworkInterface.getByInetAddress(localAddress().getAddress()), null, promise); + } catch (SocketException e) { + promise.setFailure(e); + } + return promise; + } + + @Override + public ChannelFuture leaveGroup( + InetSocketAddress multicastAddress, NetworkInterface networkInterface) { + return leaveGroup(multicastAddress, networkInterface, newPromise()); + } + + @Override + public ChannelFuture leaveGroup( + InetSocketAddress multicastAddress, + NetworkInterface networkInterface, ChannelPromise promise) { + return leaveGroup(multicastAddress.getAddress(), networkInterface, null, promise); + } + + @Override + public ChannelFuture leaveGroup( + InetAddress multicastAddress, NetworkInterface networkInterface, InetAddress source) { + return leaveGroup(multicastAddress, networkInterface, source, newPromise()); + } + + @Override + public ChannelFuture leaveGroup( + final InetAddress multicastAddress, final NetworkInterface networkInterface, final InetAddress source, + final ChannelPromise promise) { + if (multicastAddress == null) { + throw new NullPointerException("multicastAddress"); + } + if (networkInterface == null) { + throw new NullPointerException("networkInterface"); + } + + promise.setFailure(new UnsupportedOperationException("Multicast not supported")); + + return promise; + } + + @Override + public ChannelFuture block( + InetAddress multicastAddress, NetworkInterface networkInterface, + InetAddress sourceToBlock) { + return block(multicastAddress, networkInterface, sourceToBlock, newPromise()); + } + + @Override + public ChannelFuture block( + final InetAddress multicastAddress, final NetworkInterface networkInterface, + final InetAddress sourceToBlock, final ChannelPromise promise) { + if (multicastAddress == null) { + throw new NullPointerException("multicastAddress"); + } + if (sourceToBlock == null) { + throw new NullPointerException("sourceToBlock"); + } + + if (networkInterface == null) { + throw new NullPointerException("networkInterface"); + } + promise.setFailure(new UnsupportedOperationException("Multicast not supported")); + return promise; + } + + @Override + public ChannelFuture block(InetAddress multicastAddress, InetAddress sourceToBlock) { + return block(multicastAddress, sourceToBlock, newPromise()); + } + + @Override + public ChannelFuture block( + InetAddress multicastAddress, InetAddress sourceToBlock, ChannelPromise promise) { + try { + return block( + multicastAddress, + NetworkInterface.getByInetAddress(localAddress().getAddress()), + sourceToBlock, promise); + } catch (Throwable e) { + promise.setFailure(e); + } + return promise; + } + + @Override + protected AbstractKQueueUnsafe newUnsafe() { + return new KQueueDatagramChannelUnsafe(); + } + + @Override + protected InetSocketAddress localAddress0() { + return local; + } + + @Override + protected InetSocketAddress remoteAddress0() { + return remote; + } + + @Override + protected void doBind(SocketAddress localAddress) throws Exception { + InetSocketAddress addr = (InetSocketAddress) localAddress; + checkResolvable(addr); + socket.bind(addr); + local = socket.localAddress(); + active = true; + } + + @Override + protected void doWrite(ChannelOutboundBuffer in) throws Exception { + for (;;) { + Object msg = in.current(); + if (msg == null) { + // Wrote all messages. + writeFilter(false); + break; + } + + try { + boolean done = false; + for (int i = config().getWriteSpinCount(); i > 0; --i) { + if (doWriteMessage(msg)) { + done = true; + break; + } + } + + if (done) { + in.remove(); + } else { + // Did not write all messages. + writeFilter(true); + break; + } + } catch (IOException e) { + // Continue on write error as a DatagramChannel can write to multiple remote peers + // + // See https://github.com/netty/netty/issues/2665 + in.remove(e); + } + } + } + + private boolean doWriteMessage(Object msg) throws Exception { + final ByteBuf data; + InetSocketAddress remoteAddress; + if (msg instanceof AddressedEnvelope) { + @SuppressWarnings("unchecked") + AddressedEnvelope envelope = + (AddressedEnvelope) msg; + data = envelope.content(); + remoteAddress = envelope.recipient(); + } else { + data = (ByteBuf) msg; + remoteAddress = null; + } + + final int dataLen = data.readableBytes(); + if (dataLen == 0) { + return true; + } + + if (remoteAddress == null) { + remoteAddress = remote; + if (remoteAddress == null) { + throw new NotYetConnectedException(); + } + } + + final int writtenBytes; + if (data.hasMemoryAddress()) { + long memoryAddress = data.memoryAddress(); + writtenBytes = socket.sendToAddress(memoryAddress, data.readerIndex(), data.writerIndex(), + remoteAddress.getAddress(), remoteAddress.getPort()); + } else if (data instanceof CompositeByteBuf) { + IovArray array = ((KQueueEventLoop) eventLoop()).cleanArray(); + array.add(data); + int cnt = array.count(); + assert cnt != 0; + + writtenBytes = socket.sendToAddresses(array.memoryAddress(0), + cnt, remoteAddress.getAddress(), remoteAddress.getPort()); + } else { + ByteBuffer nioData = data.internalNioBuffer(data.readerIndex(), data.readableBytes()); + writtenBytes = socket.sendTo(nioData, nioData.position(), nioData.limit(), + remoteAddress.getAddress(), remoteAddress.getPort()); + } + + return writtenBytes > 0; + } + + @Override + protected Object filterOutboundMessage(Object msg) { + if (msg instanceof DatagramPacket) { + DatagramPacket packet = (DatagramPacket) msg; + ByteBuf content = packet.content(); + if (content.hasMemoryAddress()) { + return msg; + } + + if (content.isDirect() && content instanceof CompositeByteBuf) { + // Special handling of CompositeByteBuf to reduce memory copies if some of the Components + // in the CompositeByteBuf are backed by a memoryAddress. + CompositeByteBuf comp = (CompositeByteBuf) content; + if (comp.isDirect() && comp.nioBufferCount() <= IOV_MAX) { + return msg; + } + } + // We can only handle direct buffers so we need to copy if a non direct is + // passed to write. + return new DatagramPacket(newDirectBuffer(packet, content), packet.recipient()); + } + + if (msg instanceof ByteBuf) { + ByteBuf buf = (ByteBuf) msg; + if (!buf.hasMemoryAddress() && (PlatformDependent.hasUnsafe() || !buf.isDirect())) { + if (buf instanceof CompositeByteBuf) { + // Special handling of CompositeByteBuf to reduce memory copies if some of the Components + // in the CompositeByteBuf are backed by a memoryAddress. + CompositeByteBuf comp = (CompositeByteBuf) buf; + if (!comp.isDirect() || comp.nioBufferCount() > IOV_MAX) { + // more then 1024 buffers for gathering writes so just do a memory copy. + buf = newDirectBuffer(buf); + assert buf.hasMemoryAddress(); + } + } else { + // We can only handle buffers with memory address so we need to copy if a non direct is + // passed to write. + buf = newDirectBuffer(buf); + assert buf.hasMemoryAddress(); + } + } + return buf; + } + + if (msg instanceof AddressedEnvelope) { + @SuppressWarnings("unchecked") + AddressedEnvelope e = (AddressedEnvelope) msg; + if (e.content() instanceof ByteBuf && + (e.recipient() == null || e.recipient() instanceof InetSocketAddress)) { + + ByteBuf content = (ByteBuf) e.content(); + if (content.hasMemoryAddress()) { + return e; + } + if (content instanceof CompositeByteBuf) { + // Special handling of CompositeByteBuf to reduce memory copies if some of the Components + // in the CompositeByteBuf are backed by a memoryAddress. + CompositeByteBuf comp = (CompositeByteBuf) content; + if (comp.isDirect() && comp.nioBufferCount() <= IOV_MAX) { + return e; + } + } + // We can only handle direct buffers so we need to copy if a non direct is + // passed to write. + return new DefaultAddressedEnvelope( + newDirectBuffer(e, content), (InetSocketAddress) e.recipient()); + } + } + + throw new UnsupportedOperationException( + "unsupported message type: " + StringUtil.simpleClassName(msg) + EXPECTED_TYPES); + } + + @Override + public KQueueDatagramChannelConfig config() { + return config; + } + + @Override + protected void doDisconnect() throws Exception { + connected = false; + } + + final class KQueueDatagramChannelUnsafe extends AbstractKQueueUnsafe { + private final List readBuf = new ArrayList(); + + @Override + public void connect(SocketAddress remote, SocketAddress local, ChannelPromise channelPromise) { + boolean success = false; + try { + try { + boolean wasActive = isActive(); + InetSocketAddress remoteAddress = (InetSocketAddress) remote; + if (local != null) { + InetSocketAddress localAddress = (InetSocketAddress) local; + doBind(localAddress); + } + + checkResolvable(remoteAddress); + KQueueDatagramChannel.this.remote = remoteAddress; + KQueueDatagramChannel.this.local = socket.localAddress(); + success = true; + + // First notify the promise before notifying the handler. + channelPromise.trySuccess(); + + // Regardless if the connection attempt was cancelled, channelActive() event should be triggered, + // because what happened is what happened. + if (!wasActive && isActive()) { + pipeline().fireChannelActive(); + } + } finally { + if (!success) { + doClose(); + } else { + connected = true; + } + } + } catch (Throwable cause) { + channelPromise.tryFailure(cause); + } + } + + @Override + void readReady(KQueueRecvByteAllocatorHandle allocHandle) { + assert eventLoop().inEventLoop(); + final DatagramChannelConfig config = config(); + if (shouldBreakReadReady(config)) { + clearReadFilter0(); + return; + } + final ChannelPipeline pipeline = pipeline(); + final ByteBufAllocator allocator = config.getAllocator(); + allocHandle.reset(config); + readReadyBefore(); + + Throwable exception = null; + try { + ByteBuf data = null; + try { + do { + data = allocHandle.allocate(allocator); + allocHandle.attemptedBytesRead(data.writableBytes()); + final DatagramSocketAddress remoteAddress; + if (data.hasMemoryAddress()) { + // has a memory address so use optimized call + remoteAddress = socket.recvFromAddress(data.memoryAddress(), data.writerIndex(), + data.capacity()); + } else { + ByteBuffer nioData = data.internalNioBuffer(data.writerIndex(), data.writableBytes()); + remoteAddress = socket.recvFrom(nioData, nioData.position(), nioData.limit()); + } + + if (remoteAddress == null) { + allocHandle.lastBytesRead(-1); + data.release(); + data = null; + break; + } + + allocHandle.incMessagesRead(1); + allocHandle.lastBytesRead(remoteAddress.receivedAmount()); + data.writerIndex(data.writerIndex() + allocHandle.lastBytesRead()); + + readBuf.add(new DatagramPacket(data, (InetSocketAddress) localAddress(), remoteAddress)); + data = null; + } while (allocHandle.continueReading()); + } catch (Throwable t) { + if (data != null) { + data.release(); + } + exception = t; + } + + int size = readBuf.size(); + for (int i = 0; i < size; i ++) { + readPending = false; + pipeline.fireChannelRead(readBuf.get(i)); + } + readBuf.clear(); + allocHandle.readComplete(); + pipeline.fireChannelReadComplete(); + + if (exception != null) { + pipeline.fireExceptionCaught(exception); + } + } finally { + readReadyFinally(config); + } + } + } +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueDatagramChannelConfig.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueDatagramChannelConfig.java new file mode 100644 index 0000000000..c64417485b --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueDatagramChannelConfig.java @@ -0,0 +1,387 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.ChannelException; +import io.netty.channel.ChannelOption; +import io.netty.channel.FixedRecvByteBufAllocator; +import io.netty.channel.MessageSizeEstimator; +import io.netty.channel.RecvByteBufAllocator; +import io.netty.channel.WriteBufferWaterMark; +import io.netty.channel.socket.DatagramChannelConfig; +import io.netty.util.internal.UnstableApi; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.Map; + +import static io.netty.channel.ChannelOption.DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION; +import static io.netty.channel.ChannelOption.IP_MULTICAST_ADDR; +import static io.netty.channel.ChannelOption.IP_MULTICAST_IF; +import static io.netty.channel.ChannelOption.IP_MULTICAST_LOOP_DISABLED; +import static io.netty.channel.ChannelOption.IP_MULTICAST_TTL; +import static io.netty.channel.ChannelOption.IP_TOS; +import static io.netty.channel.ChannelOption.SO_BROADCAST; +import static io.netty.channel.ChannelOption.SO_RCVBUF; +import static io.netty.channel.ChannelOption.SO_REUSEADDR; +import static io.netty.channel.ChannelOption.SO_SNDBUF; +import static io.netty.channel.unix.UnixChannelOption.SO_REUSEPORT; + +@UnstableApi +public final class KQueueDatagramChannelConfig extends KQueueChannelConfig implements DatagramChannelConfig { + private static final RecvByteBufAllocator DEFAULT_RCVBUF_ALLOCATOR = new FixedRecvByteBufAllocator(2048); + private final KQueueDatagramChannel datagramChannel; + private boolean activeOnOpen; + + KQueueDatagramChannelConfig(KQueueDatagramChannel channel) { + super(channel); + this.datagramChannel = channel; + setRecvByteBufAllocator(DEFAULT_RCVBUF_ALLOCATOR); + } + + @Override + @SuppressWarnings("deprecation") + public Map, Object> getOptions() { + return getOptions( + super.getOptions(), + SO_BROADCAST, SO_RCVBUF, SO_SNDBUF, SO_REUSEADDR, IP_MULTICAST_LOOP_DISABLED, + IP_MULTICAST_ADDR, IP_MULTICAST_IF, IP_MULTICAST_TTL, + IP_TOS, DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION, SO_REUSEPORT); + } + + @SuppressWarnings({ "unchecked", "deprecation" }) + @Override + public T getOption(ChannelOption option) { + if (option == SO_BROADCAST) { + return (T) Boolean.valueOf(isBroadcast()); + } + if (option == SO_RCVBUF) { + return (T) Integer.valueOf(getReceiveBufferSize()); + } + if (option == SO_SNDBUF) { + return (T) Integer.valueOf(getSendBufferSize()); + } + if (option == SO_REUSEADDR) { + return (T) Boolean.valueOf(isReuseAddress()); + } + if (option == IP_MULTICAST_LOOP_DISABLED) { + return (T) Boolean.valueOf(isLoopbackModeDisabled()); + } + if (option == IP_MULTICAST_ADDR) { + return (T) getInterface(); + } + if (option == IP_MULTICAST_IF) { + return (T) getNetworkInterface(); + } + if (option == IP_MULTICAST_TTL) { + return (T) Integer.valueOf(getTimeToLive()); + } + if (option == IP_TOS) { + return (T) Integer.valueOf(getTrafficClass()); + } + if (option == DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION) { + return (T) Boolean.valueOf(activeOnOpen); + } + if (option == SO_REUSEPORT) { + return (T) Boolean.valueOf(isReusePort()); + } + return super.getOption(option); + } + + @Override + @SuppressWarnings("deprecation") + public boolean setOption(ChannelOption option, T value) { + validate(option, value); + + if (option == SO_BROADCAST) { + setBroadcast((Boolean) value); + } else if (option == SO_RCVBUF) { + setReceiveBufferSize((Integer) value); + } else if (option == SO_SNDBUF) { + setSendBufferSize((Integer) value); + } else if (option == SO_REUSEADDR) { + setReuseAddress((Boolean) value); + } else if (option == IP_MULTICAST_LOOP_DISABLED) { + setLoopbackModeDisabled((Boolean) value); + } else if (option == IP_MULTICAST_ADDR) { + setInterface((InetAddress) value); + } else if (option == IP_MULTICAST_IF) { + setNetworkInterface((NetworkInterface) value); + } else if (option == IP_MULTICAST_TTL) { + setTimeToLive((Integer) value); + } else if (option == IP_TOS) { + setTrafficClass((Integer) value); + } else if (option == DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION) { + setActiveOnOpen((Boolean) value); + } else if (option == SO_REUSEPORT) { + setReusePort((Boolean) value); + } else { + return super.setOption(option, value); + } + + return true; + } + + private void setActiveOnOpen(boolean activeOnOpen) { + if (channel.isRegistered()) { + throw new IllegalStateException("Can only changed before channel was registered"); + } + this.activeOnOpen = activeOnOpen; + } + + boolean getActiveOnOpen() { + return activeOnOpen; + } + + /** + * Returns {@code true} if the SO_REUSEPORT option is set. + */ + public boolean isReusePort() { + try { + return datagramChannel.socket.isReusePort(); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + /** + * Set the SO_REUSEPORT option on the underlying Channel. This will allow to bind multiple + * {@link KQueueSocketChannel}s to the same port and so accept connections with multiple threads. + * + * Be aware this method needs be called before {@link KQueueDatagramChannel#bind(java.net.SocketAddress)} to have + * any affect. + */ + public KQueueDatagramChannelConfig setReusePort(boolean reusePort) { + try { + datagramChannel.socket.setReusePort(reusePort); + return this; + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public KQueueDatagramChannelConfig setRcvAllocTransportProvidesGuess(boolean transportProvidesGuess) { + super.setRcvAllocTransportProvidesGuess(transportProvidesGuess); + return this; + } + + @Override + public KQueueDatagramChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator) { + super.setMessageSizeEstimator(estimator); + return this; + } + + @Override + @Deprecated + public KQueueDatagramChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark) { + super.setWriteBufferLowWaterMark(writeBufferLowWaterMark); + return this; + } + + @Override + @Deprecated + public KQueueDatagramChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark) { + super.setWriteBufferHighWaterMark(writeBufferHighWaterMark); + return this; + } + + @Override + public KQueueDatagramChannelConfig setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark) { + super.setWriteBufferWaterMark(writeBufferWaterMark); + return this; + } + + @Override + public KQueueDatagramChannelConfig setAutoClose(boolean autoClose) { + super.setAutoClose(autoClose); + return this; + } + + @Override + public KQueueDatagramChannelConfig setAutoRead(boolean autoRead) { + super.setAutoRead(autoRead); + return this; + } + + @Override + public KQueueDatagramChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator) { + super.setRecvByteBufAllocator(allocator); + return this; + } + + @Override + public KQueueDatagramChannelConfig setWriteSpinCount(int writeSpinCount) { + super.setWriteSpinCount(writeSpinCount); + return this; + } + + @Override + public KQueueDatagramChannelConfig setAllocator(ByteBufAllocator allocator) { + super.setAllocator(allocator); + return this; + } + + @Override + public KQueueDatagramChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis) { + super.setConnectTimeoutMillis(connectTimeoutMillis); + return this; + } + + @Override + @Deprecated + public KQueueDatagramChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead) { + super.setMaxMessagesPerRead(maxMessagesPerRead); + return this; + } + + @Override + public int getSendBufferSize() { + try { + return datagramChannel.socket.getSendBufferSize(); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public KQueueDatagramChannelConfig setSendBufferSize(int sendBufferSize) { + try { + datagramChannel.socket.setSendBufferSize(sendBufferSize); + return this; + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public int getReceiveBufferSize() { + try { + return datagramChannel.socket.getReceiveBufferSize(); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public KQueueDatagramChannelConfig setReceiveBufferSize(int receiveBufferSize) { + try { + datagramChannel.socket.setReceiveBufferSize(receiveBufferSize); + return this; + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public int getTrafficClass() { + try { + return datagramChannel.socket.getTrafficClass(); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public KQueueDatagramChannelConfig setTrafficClass(int trafficClass) { + try { + datagramChannel.socket.setTrafficClass(trafficClass); + return this; + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public boolean isReuseAddress() { + try { + return datagramChannel.socket.isReuseAddress(); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public KQueueDatagramChannelConfig setReuseAddress(boolean reuseAddress) { + try { + datagramChannel.socket.setReuseAddress(reuseAddress); + return this; + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public boolean isBroadcast() { + try { + return datagramChannel.socket.isBroadcast(); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public KQueueDatagramChannelConfig setBroadcast(boolean broadcast) { + try { + datagramChannel.socket.setBroadcast(broadcast); + return this; + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public boolean isLoopbackModeDisabled() { + return false; + } + + @Override + public DatagramChannelConfig setLoopbackModeDisabled(boolean loopbackModeDisabled) { + throw new UnsupportedOperationException("Multicast not supported"); + } + + @Override + public int getTimeToLive() { + return -1; + } + + @Override + public KQueueDatagramChannelConfig setTimeToLive(int ttl) { + throw new UnsupportedOperationException("Multicast not supported"); + } + + @Override + public InetAddress getInterface() { + return null; + } + + @Override + public KQueueDatagramChannelConfig setInterface(InetAddress interfaceAddress) { + throw new UnsupportedOperationException("Multicast not supported"); + } + + @Override + public NetworkInterface getNetworkInterface() { + return null; + } + + @Override + public KQueueDatagramChannelConfig setNetworkInterface(NetworkInterface networkInterface) { + throw new UnsupportedOperationException("Multicast not supported"); + } +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueDomainSocketChannel.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueDomainSocketChannel.java new file mode 100644 index 0000000000..ff99c51382 --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueDomainSocketChannel.java @@ -0,0 +1,183 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelConfig; +import io.netty.channel.ChannelOutboundBuffer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.unix.DomainSocketAddress; +import io.netty.channel.unix.DomainSocketChannel; +import io.netty.channel.unix.FileDescriptor; +import io.netty.channel.unix.PeerCredentials; +import io.netty.util.internal.UnstableApi; + +import java.io.IOException; +import java.net.SocketAddress; + +import static io.netty.channel.kqueue.BsdSocket.newSocketDomain; + +@UnstableApi +public final class KQueueDomainSocketChannel extends AbstractKQueueStreamChannel implements DomainSocketChannel { + private final KQueueDomainSocketChannelConfig config = new KQueueDomainSocketChannelConfig(this); + + private volatile DomainSocketAddress local; + private volatile DomainSocketAddress remote; + + public KQueueDomainSocketChannel() { + super(null, newSocketDomain(), false); + } + + KQueueDomainSocketChannel(Channel parent, BsdSocket fd) { + super(parent, fd, true); + } + + @Override + protected AbstractKQueueUnsafe newUnsafe() { + return new KQueueDomainUnsafe(); + } + + @Override + protected DomainSocketAddress localAddress0() { + return local; + } + + @Override + protected DomainSocketAddress remoteAddress0() { + return remote; + } + + @Override + protected void doBind(SocketAddress localAddress) throws Exception { + socket.bind(localAddress); + local = (DomainSocketAddress) localAddress; + } + + @Override + public KQueueDomainSocketChannelConfig config() { + return config; + } + + @Override + protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception { + if (super.doConnect(remoteAddress, localAddress)) { + local = (DomainSocketAddress) localAddress; + remote = (DomainSocketAddress) remoteAddress; + return true; + } + return false; + } + + @Override + public DomainSocketAddress remoteAddress() { + return (DomainSocketAddress) super.remoteAddress(); + } + + @Override + public DomainSocketAddress localAddress() { + return (DomainSocketAddress) super.localAddress(); + } + + @Override + protected boolean doWriteSingle(ChannelOutboundBuffer in, int writeSpinCount) throws Exception { + Object msg = in.current(); + if (msg instanceof FileDescriptor && socket.sendFd(((FileDescriptor) msg).intValue()) > 0) { + // File descriptor was written, so remove it. + in.remove(); + return true; + } + return super.doWriteSingle(in, writeSpinCount); + } + + @Override + protected Object filterOutboundMessage(Object msg) { + if (msg instanceof FileDescriptor) { + return msg; + } + return super.filterOutboundMessage(msg); + } + + /** + * Returns the unix credentials (uid, gid, pid) of the peer + * SO_PEERCRED + */ + @UnstableApi + public PeerCredentials peerCredentials() throws IOException { + return socket.getPeerCredentials(); + } + + private final class KQueueDomainUnsafe extends KQueueStreamUnsafe { + @Override + void readReady(KQueueRecvByteAllocatorHandle allocHandle) { + switch (config().getReadMode()) { + case BYTES: + super.readReady(allocHandle); + break; + case FILE_DESCRIPTORS: + readReadyFd(); + break; + default: + throw new Error(); + } + } + + private void readReadyFd() { + if (socket.isInputShutdown()) { + super.clearReadFilter0(); + return; + } + final ChannelConfig config = config(); + final KQueueRecvByteAllocatorHandle allocHandle = recvBufAllocHandle(); + + final ChannelPipeline pipeline = pipeline(); + allocHandle.reset(config); + readReadyBefore(); + + try { + readLoop: do { + // lastBytesRead represents the fd. We use lastBytesRead because it must be set so that the + // KQueueRecvByteAllocatorHandle knows if it should try to read again or not when autoRead is + // enabled. + int recvFd = socket.recvFd(); + switch(recvFd) { + case 0: + allocHandle.lastBytesRead(0); + break readLoop; + case -1: + allocHandle.lastBytesRead(-1); + close(voidPromise()); + return; + default: + allocHandle.lastBytesRead(1); + allocHandle.incMessagesRead(1); + readPending = false; + pipeline.fireChannelRead(new FileDescriptor(recvFd)); + break; + } + } while (allocHandle.continueReading()); + + allocHandle.readComplete(); + pipeline.fireChannelReadComplete(); + } catch (Throwable t) { + allocHandle.readComplete(); + pipeline.fireChannelReadComplete(); + pipeline.fireExceptionCaught(t); + } finally { + readReadyFinally(config); + } + } + } +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueDomainSocketChannelConfig.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueDomainSocketChannelConfig.java new file mode 100644 index 0000000000..eefd4c0ae1 --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueDomainSocketChannelConfig.java @@ -0,0 +1,154 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.ChannelOption; +import io.netty.channel.MessageSizeEstimator; +import io.netty.channel.RecvByteBufAllocator; +import io.netty.channel.WriteBufferWaterMark; +import io.netty.channel.unix.DomainSocketChannelConfig; +import io.netty.channel.unix.DomainSocketReadMode; +import io.netty.util.internal.UnstableApi; + +import java.util.Map; + +import static io.netty.channel.unix.UnixChannelOption.DOMAIN_SOCKET_READ_MODE; + +@UnstableApi +public final class KQueueDomainSocketChannelConfig extends KQueueChannelConfig implements DomainSocketChannelConfig { + private volatile DomainSocketReadMode mode = DomainSocketReadMode.BYTES; + + KQueueDomainSocketChannelConfig(AbstractKQueueChannel channel) { + super(channel); + } + + @Override + public Map, Object> getOptions() { + return getOptions(super.getOptions(), DOMAIN_SOCKET_READ_MODE); + } + + @SuppressWarnings("unchecked") + @Override + public T getOption(ChannelOption option) { + if (option == DOMAIN_SOCKET_READ_MODE) { + return (T) getReadMode(); + } + return super.getOption(option); + } + + @Override + public boolean setOption(ChannelOption option, T value) { + validate(option, value); + + if (option == DOMAIN_SOCKET_READ_MODE) { + setReadMode((DomainSocketReadMode) value); + } else { + return super.setOption(option, value); + } + + return true; + } + + @Override + public KQueueDomainSocketChannelConfig setRcvAllocTransportProvidesGuess(boolean transportProvidesGuess) { + super.setRcvAllocTransportProvidesGuess(transportProvidesGuess); + return this; + } + + @Override + @Deprecated + public KQueueDomainSocketChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead) { + super.setMaxMessagesPerRead(maxMessagesPerRead); + return this; + } + + @Override + public KQueueDomainSocketChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis) { + super.setConnectTimeoutMillis(connectTimeoutMillis); + return this; + } + + @Override + public KQueueDomainSocketChannelConfig setWriteSpinCount(int writeSpinCount) { + super.setWriteSpinCount(writeSpinCount); + return this; + } + + @Override + public KQueueDomainSocketChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator) { + super.setRecvByteBufAllocator(allocator); + return this; + } + + @Override + public KQueueDomainSocketChannelConfig setAllocator(ByteBufAllocator allocator) { + super.setAllocator(allocator); + return this; + } + + @Override + public KQueueDomainSocketChannelConfig setAutoClose(boolean autoClose) { + super.setAutoClose(autoClose); + return this; + } + + @Override + public KQueueDomainSocketChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator) { + super.setMessageSizeEstimator(estimator); + return this; + } + + @Override + @Deprecated + public KQueueDomainSocketChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark) { + super.setWriteBufferLowWaterMark(writeBufferLowWaterMark); + return this; + } + + @Override + @Deprecated + public KQueueDomainSocketChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark) { + super.setWriteBufferHighWaterMark(writeBufferHighWaterMark); + return this; + } + + @Override + public KQueueDomainSocketChannelConfig setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark) { + super.setWriteBufferWaterMark(writeBufferWaterMark); + return this; + } + + @Override + public KQueueDomainSocketChannelConfig setAutoRead(boolean autoRead) { + super.setAutoRead(autoRead); + return this; + } + + @Override + public KQueueDomainSocketChannelConfig setReadMode(DomainSocketReadMode mode) { + if (mode == null) { + throw new NullPointerException("mode"); + } + this.mode = mode; + return this; + } + + @Override + public DomainSocketReadMode getReadMode() { + return mode; + } +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueEventArray.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueEventArray.java new file mode 100644 index 0000000000..636d55a814 --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueEventArray.java @@ -0,0 +1,144 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.util.internal.PlatformDependent; + +/** + * Represents an array of kevent structures, backed by offheap memory. + * + * struct kevent { + * uintptr_t ident; + * short keventFilter; + * u_short flags; + * u_int fflags; + * intptr_t data; + * void *udata; + * }; + */ +final class KQueueEventArray { + private static final int KQUEUE_EVENT_SIZE = Native.sizeofKEvent(); + private static final int KQUEUE_IDENT_OFFSET = Native.offsetofKEventIdent(); + private static final int KQUEUE_FILTER_OFFSET = Native.offsetofKEventFilter(); + private static final int KQUEUE_FFLAGS_OFFSET = Native.offsetofKEventFFlags(); + private static final int KQUEUE_FLAGS_OFFSET = Native.offsetofKEventFlags(); + private static final int KQUEUE_DATA_OFFSET = Native.offsetofKeventData(); + + private long memoryAddress; + private int size; + private int capacity; + + KQueueEventArray(int capacity) { + if (capacity < 1) { + throw new IllegalArgumentException("capacity must be >= 1 but was " + capacity); + } + memoryAddress = PlatformDependent.allocateMemory(capacity * KQUEUE_EVENT_SIZE); + this.capacity = capacity; + } + + /** + * Return the {@code memoryAddress} which points to the start of this {@link KQueueEventArray}. + */ + long memoryAddress() { + return memoryAddress; + } + + /** + * Return the capacity of the {@link KQueueEventArray} which represent the maximum number of {@code kevent}s + * that can be stored in it. + */ + int capacity() { + return capacity; + } + + int size() { + return size; + } + + void clear() { + size = 0; + } + + void evSet(AbstractKQueueChannel ch, short filter, short flags, int fflags) { + checkSize(); + evSet(getKEventOffset(size++), ch, ch.socket.intValue(), filter, flags, fflags); + } + + private void checkSize() { + if (size == capacity) { + realloc(true); + } + } + + /** + * Increase the storage of this {@link KQueueEventArray}. + */ + void realloc(boolean throwIfFail) { + // Double the capacity while it is "sufficiently small", and otherwise increase by 50%. + int newLength = capacity <= 65536 ? capacity << 1 : capacity + capacity >> 1; + long newMemoryAddress = PlatformDependent.reallocateMemory(memoryAddress, newLength * KQUEUE_EVENT_SIZE); + if (newMemoryAddress != 0) { + memoryAddress = newMemoryAddress; + capacity = newLength; + return; + } + if (throwIfFail) { + throw new OutOfMemoryError("unable to allocate " + newLength + " new bytes! Existing capacity is: " + + capacity); + } + } + + /** + * Free this {@link KQueueEventArray}. Any usage after calling this method may segfault the JVM! + */ + void free() { + PlatformDependent.freeMemory(memoryAddress); + memoryAddress = size = capacity = 0; + } + + long getKEventOffset(int index) { + return memoryAddress + index * KQUEUE_EVENT_SIZE; + } + + short flags(int index) { + return PlatformDependent.getShort(getKEventOffset(index) + KQUEUE_FLAGS_OFFSET); + } + + short filter(int index) { + return PlatformDependent.getShort(getKEventOffset(index) + KQUEUE_FILTER_OFFSET); + } + + short fflags(int index) { + return PlatformDependent.getShort(getKEventOffset(index) + KQUEUE_FFLAGS_OFFSET); + } + + int fd(int index) { + return PlatformDependent.getInt(getKEventOffset(index) + KQUEUE_IDENT_OFFSET); + } + + long data(int index) { + return PlatformDependent.getLong(getKEventOffset(index) + KQUEUE_DATA_OFFSET); + } + + AbstractKQueueChannel channel(int index) { + return getChannel(getKEventOffset(index)); + } + + private static native void evSet(long keventAddress, AbstractKQueueChannel ch, + int ident, short filter, short flags, int fflags); + private static native AbstractKQueueChannel getChannel(long keventAddress); + static native void deleteGlobalRefs(long channelAddressStart, long channelAddressEnd); +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueEventLoop.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueEventLoop.java new file mode 100644 index 0000000000..00bf0744a2 --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueEventLoop.java @@ -0,0 +1,370 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.channel.EventLoop; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.SelectStrategy; +import io.netty.channel.SingleThreadEventLoop; +import io.netty.channel.kqueue.AbstractKQueueChannel.AbstractKQueueUnsafe; +import io.netty.channel.unix.FileDescriptor; +import io.netty.channel.unix.IovArray; +import io.netty.util.IntSupplier; +import io.netty.util.concurrent.RejectedExecutionHandler; +import io.netty.util.internal.ObjectUtil; +import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; + +import java.io.IOException; +import java.util.Queue; +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; + +import static io.netty.channel.kqueue.KQueueEventArray.deleteGlobalRefs; +import static java.lang.Math.min; + +/** + * {@link EventLoop} which uses kqueue under the covers. Only works on BSD! + */ +final class KQueueEventLoop extends SingleThreadEventLoop { + private static final InternalLogger logger = InternalLoggerFactory.getInstance(KQueueEventLoop.class); + private static final AtomicIntegerFieldUpdater WAKEN_UP_UPDATER = + AtomicIntegerFieldUpdater.newUpdater(KQueueEventLoop.class, "wakenUp"); + private static final int KQUEUE_WAKE_UP_IDENT = 0; + + static { + // Ensure JNI is initialized by the time this class is loaded by this time! + // We use unix-common methods in this class which are backed by JNI methods. + KQueue.ensureAvailability(); + } + + private final NativeLongArray jniChannelPointers; + private final boolean allowGrowing; + private final FileDescriptor kqueueFd; + private final KQueueEventArray changeList; + private final KQueueEventArray eventList; + private final SelectStrategy selectStrategy; + private final IovArray iovArray = new IovArray(); + private final IntSupplier selectNowSupplier = new IntSupplier() { + @Override + public int get() throws Exception { + return kqueueWaitNow(); + } + }; + private final Callable pendingTasksCallable = new Callable() { + @Override + public Integer call() throws Exception { + return KQueueEventLoop.super.pendingTasks(); + } + }; + + private volatile int wakenUp; + private volatile int ioRatio = 50; + + KQueueEventLoop(EventLoopGroup parent, Executor executor, int maxEvents, + SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) { + super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler); + selectStrategy = ObjectUtil.checkNotNull(strategy, "strategy"); + this.kqueueFd = Native.newKQueue(); + if (maxEvents == 0) { + allowGrowing = true; + maxEvents = 4096; + } else { + allowGrowing = false; + } + changeList = new KQueueEventArray(maxEvents); + eventList = new KQueueEventArray(maxEvents); + jniChannelPointers = new NativeLongArray(4096); + int result = Native.keventAddUserEvent(kqueueFd.intValue(), KQUEUE_WAKE_UP_IDENT); + if (result < 0) { + cleanup(); + throw new IllegalStateException("kevent failed to add user event with errno: " + (-result)); + } + } + + void evSet(AbstractKQueueChannel ch, short filter, short flags, int fflags) { + changeList.evSet(ch, filter, flags, fflags); + } + + void remove(AbstractKQueueChannel ch) throws IOException { + assert inEventLoop(); + if (ch.jniSelfPtr == 0) { + return; + } + + jniChannelPointers.add(ch.jniSelfPtr); + ch.jniSelfPtr = 0; + } + + /** + * Return a cleared {@link IovArray} that can be used for writes in this {@link EventLoop}. + */ + IovArray cleanArray() { + iovArray.clear(); + return iovArray; + } + + @Override + protected void wakeup(boolean inEventLoop) { + if (!inEventLoop && WAKEN_UP_UPDATER.compareAndSet(this, 0, 1)) { + wakeup(); + } + } + + private void wakeup() { + Native.keventTriggerUserEvent(kqueueFd.intValue(), KQUEUE_WAKE_UP_IDENT); + // Note that the result may return an error (e.g. errno = EBADF after the event loop has been shutdown). + // So it is not very practical to assert the return value is always >= 0. + } + + private int kqueueWait(boolean oldWakeup) throws IOException { + // TODO(scott): do we need to loop here ... we already loop in keventWait to ensure we wait the expected time. + // We also do the same thing in EPOLL ... do we need to loop there? + + // If a task was submitted when wakenUp value was 1, the task didn't get a chance to produce wakeup event. + // So we need to check task queue again before calling epoll_wait. If we don't, the task might be pended + // until epoll_wait was timed out. It might be pended until idle timeout if IdleStateHandler existed + // in pipeline. + if (oldWakeup && hasTasks()) { + return kqueueWaitNow(); + } + + long totalDelay = delayNanos(System.nanoTime()); + int delaySeconds = (int) min(totalDelay / 1000000000L, Integer.MAX_VALUE); + return kqueueWait(delaySeconds, (int) min(totalDelay - delaySeconds * 1000000000L, Integer.MAX_VALUE)); + } + + private int kqueueWaitNow() throws IOException { + return kqueueWait(0, 0); + } + + private int kqueueWait(int timeoutSec, int timeoutNs) throws IOException { + deleteJniChannelPointers(); + int numEvents = Native.keventWait(kqueueFd.intValue(), changeList, eventList, timeoutSec, timeoutNs); + changeList.clear(); + return numEvents; + } + + private void deleteJniChannelPointers() { + if (!jniChannelPointers.isEmpty()) { + deleteGlobalRefs(jniChannelPointers.memoryAddress(), jniChannelPointers.memoryAddressEnd()); + jniChannelPointers.clear(); + } + } + + private void processReady(int ready) { + for (int i = 0; i < ready; ++i) { + final short filter = eventList.filter(i); + final short flags = eventList.flags(i); + if (filter == Native.EVFILT_USER || (flags & Native.EV_ERROR) != 0) { + // EV_ERROR is returned if the FD is closed synchronously (which removes from kqueue) and then + // we later attempt to delete the filters from kqueue. + assert filter != Native.EVFILT_USER || + (filter == Native.EVFILT_USER && eventList.fd(i) == KQUEUE_WAKE_UP_IDENT); + continue; + } + + AbstractKQueueChannel channel = eventList.channel(i); + if (channel == null) { + // This may happen if the channel has already been closed, and it will be removed from kqueue anyways. + // We also handle EV_ERROR above to skip this even early if it is a result of a referencing a closed and + // thus removed from kqueue FD. + logger.warn("events[{}]=[{}, {}] had no channel!", i, eventList.fd(i), filter); + continue; + } + + AbstractKQueueUnsafe unsafe = (AbstractKQueueUnsafe) channel.unsafe(); + // First check for EPOLLOUT as we may need to fail the connect ChannelPromise before try + // to read from the file descriptor. + if (filter == Native.EVFILT_WRITE) { + unsafe.writeReady(); + } else if (filter == Native.EVFILT_READ) { + // Check READ before EOF to ensure all data is read before shutting down the input. + unsafe.readReady(eventList.data(i)); + } + + // Check if EV_EOF was set, this will notify us for connection-reset in which case + // we may close the channel directly or try to read more data depending on the state of the + // Channel and also depending on the AbstractKQueueChannel subtype. + if ((flags & Native.EV_EOF) != 0) { + unsafe.readEOF(); + } + } + } + + @Override + protected void run() { + for (;;) { + try { + int strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks()); + switch (strategy) { + case SelectStrategy.CONTINUE: + continue; + case SelectStrategy.SELECT: + strategy = kqueueWait(WAKEN_UP_UPDATER.getAndSet(this, 0) == 1); + + // 'wakenUp.compareAndSet(false, true)' is always evaluated + // before calling 'selector.wakeup()' to reduce the wake-up + // overhead. (Selector.wakeup() is an expensive operation.) + // + // However, there is a race condition in this approach. + // The race condition is triggered when 'wakenUp' is set to + // true too early. + // + // 'wakenUp' is set to true too early if: + // 1) Selector is waken up between 'wakenUp.set(false)' and + // 'selector.select(...)'. (BAD) + // 2) Selector is waken up between 'selector.select(...)' and + // 'if (wakenUp.get()) { ... }'. (OK) + // + // In the first case, 'wakenUp' is set to true and the + // following 'selector.select(...)' will wake up immediately. + // Until 'wakenUp' is set to false again in the next round, + // 'wakenUp.compareAndSet(false, true)' will fail, and therefore + // any attempt to wake up the Selector will fail, too, causing + // the following 'selector.select(...)' call to block + // unnecessarily. + // + // To fix this problem, we wake up the selector again if wakenUp + // is true immediately after selector.select(...). + // It is inefficient in that it wakes up the selector for both + // the first case (BAD - wake-up required) and the second case + // (OK - no wake-up required). + + if (wakenUp == 1) { + wakeup(); + } + default: + // fallthrough + } + + final int ioRatio = this.ioRatio; + if (ioRatio == 100) { + try { + if (strategy > 0) { + processReady(strategy); + } + } finally { + runAllTasks(); + } + } else { + final long ioStartTime = System.nanoTime(); + + try { + if (strategy > 0) { + processReady(strategy); + } + } finally { + final long ioTime = System.nanoTime() - ioStartTime; + runAllTasks(ioTime * (100 - ioRatio) / ioRatio); + } + } + if (allowGrowing && strategy == eventList.capacity()) { + //increase the size of the array as we needed the whole space for the events + eventList.realloc(false); + } + } catch (Throwable t) { + handleLoopException(t); + } + // Always handle shutdown even if the loop processing threw an exception. + try { + if (isShuttingDown()) { + closeAll(); + if (confirmShutdown()) { + break; + } + } + } catch (Throwable t) { + handleLoopException(t); + } + } + } + + @Override + protected Queue newTaskQueue(int maxPendingTasks) { + // This event loop never calls takeTask() + return PlatformDependent.newMpscQueue(maxPendingTasks); + } + + @Override + public int pendingTasks() { + // As we use a MpscQueue we need to ensure pendingTasks() is only executed from within the EventLoop as + // otherwise we may see unexpected behavior (as size() is only allowed to be called by a single consumer). + // See https://github.com/netty/netty/issues/5297 + return inEventLoop() ? super.pendingTasks() : submit(pendingTasksCallable).syncUninterruptibly().getNow(); + } + + /** + * Returns the percentage of the desired amount of time spent for I/O in the event loop. + */ + public int getIoRatio() { + return ioRatio; + } + + /** + * Sets the percentage of the desired amount of time spent for I/O in the event loop. The default value is + * {@code 50}, which means the event loop will try to spend the same amount of time for I/O as for non-I/O tasks. + */ + public void setIoRatio(int ioRatio) { + if (ioRatio <= 0 || ioRatio > 100) { + throw new IllegalArgumentException("ioRatio: " + ioRatio + " (expected: 0 < ioRatio <= 100)"); + } + this.ioRatio = ioRatio; + } + + @Override + protected void cleanup() { + try { + try { + kqueueFd.close(); + } catch (IOException e) { + logger.warn("Failed to close the kqueue fd.", e); + } + } finally { + // Cleanup all native memory! + + // The JNI channel pointers should already be deleted because we should wait on kevent before this method, + // but lets just be sure we cleanup native memory. + deleteJniChannelPointers(); + jniChannelPointers.free(); + + changeList.free(); + eventList.free(); + } + } + + private void closeAll() { + try { + kqueueWaitNow(); + } catch (IOException e) { + // ignore on close + } + } + + private static void handleLoopException(Throwable t) { + logger.warn("Unexpected exception in the selector loop.", t); + + // Prevent possible consecutive immediate failures that lead to + // excessive CPU consumption. + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // Ignore. + } + } +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueEventLoopGroup.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueEventLoopGroup.java new file mode 100644 index 0000000000..60fc24bc54 --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueEventLoopGroup.java @@ -0,0 +1,134 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.channel.DefaultSelectStrategyFactory; +import io.netty.channel.EventLoop; +import io.netty.channel.MultithreadEventLoopGroup; +import io.netty.channel.SelectStrategyFactory; +import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.EventExecutorChooserFactory; +import io.netty.util.concurrent.RejectedExecutionHandler; +import io.netty.util.concurrent.RejectedExecutionHandlers; +import io.netty.util.internal.UnstableApi; + +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadFactory; + +@UnstableApi +public final class KQueueEventLoopGroup extends MultithreadEventLoopGroup { + static { + // Ensure JNI is initialized by the time this class is loaded by this time! + KQueue.ensureAvailability(); + } + /** + * Create a new instance using the default number of threads and the default {@link ThreadFactory}. + */ + public KQueueEventLoopGroup() { + this(0); + } + + /** + * Create a new instance using the specified number of threads and the default {@link ThreadFactory}. + */ + public KQueueEventLoopGroup(int nThreads) { + this(nThreads, (ThreadFactory) null); + } + + /** + * Create a new instance using the specified number of threads and the default {@link ThreadFactory}. + */ + @SuppressWarnings("deprecation") + public KQueueEventLoopGroup(int nThreads, SelectStrategyFactory selectStrategyFactory) { + this(nThreads, (ThreadFactory) null, selectStrategyFactory); + } + + /** + * Create a new instance using the specified number of threads and the given {@link ThreadFactory}. + */ + @SuppressWarnings("deprecation") + public KQueueEventLoopGroup(int nThreads, ThreadFactory threadFactory) { + this(nThreads, threadFactory, 0); + } + + public KQueueEventLoopGroup(int nThreads, Executor executor) { + this(nThreads, executor, DefaultSelectStrategyFactory.INSTANCE); + } + + /** + * Create a new instance using the specified number of threads and the given {@link ThreadFactory}. + */ + @SuppressWarnings("deprecation") + public KQueueEventLoopGroup(int nThreads, ThreadFactory threadFactory, + SelectStrategyFactory selectStrategyFactory) { + this(nThreads, threadFactory, 0, selectStrategyFactory); + } + + /** + * Create a new instance using the specified number of threads, the given {@link ThreadFactory} and the given + * maximal amount of epoll events to handle per epollWait(...). + * + * @deprecated Use {@link #KQueueEventLoopGroup(int)} or {@link #KQueueEventLoopGroup(int, ThreadFactory)} + */ + @Deprecated + public KQueueEventLoopGroup(int nThreads, ThreadFactory threadFactory, int maxEventsAtOnce) { + this(nThreads, threadFactory, maxEventsAtOnce, DefaultSelectStrategyFactory.INSTANCE); + } + + /** + * Create a new instance using the specified number of threads, the given {@link ThreadFactory} and the given + * maximal amount of epoll events to handle per epollWait(...). + * + * @deprecated Use {@link #KQueueEventLoopGroup(int)}, {@link #KQueueEventLoopGroup(int, ThreadFactory)}, or + * {@link #KQueueEventLoopGroup(int, SelectStrategyFactory)} + */ + @Deprecated + public KQueueEventLoopGroup(int nThreads, ThreadFactory threadFactory, int maxEventsAtOnce, + SelectStrategyFactory selectStrategyFactory) { + super(nThreads, threadFactory, maxEventsAtOnce, selectStrategyFactory, RejectedExecutionHandlers.reject()); + } + + public KQueueEventLoopGroup(int nThreads, Executor executor, SelectStrategyFactory selectStrategyFactory) { + super(nThreads, executor, 0, selectStrategyFactory, RejectedExecutionHandlers.reject()); + } + + public KQueueEventLoopGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory, + SelectStrategyFactory selectStrategyFactory) { + super(nThreads, executor, chooserFactory, 0, selectStrategyFactory, RejectedExecutionHandlers.reject()); + } + + public KQueueEventLoopGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory, + SelectStrategyFactory selectStrategyFactory, + RejectedExecutionHandler rejectedExecutionHandler) { + super(nThreads, executor, chooserFactory, 0, selectStrategyFactory, rejectedExecutionHandler); + } + + /** + * Sets the percentage of the desired amount of time spent for I/O in the child event loops. The default value is + * {@code 50}, which means the event loop will try to spend the same amount of time for I/O as for non-I/O tasks. + */ + public void setIoRatio(int ioRatio) { + for (EventExecutor e: this) { + ((KQueueEventLoop) e).setIoRatio(ioRatio); + } + } + + @Override + protected EventLoop newChild(Executor executor, Object... args) throws Exception { + return new KQueueEventLoop(this, executor, (Integer) args[0], + ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]); + } +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueRecvByteAllocatorHandle.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueRecvByteAllocatorHandle.java new file mode 100644 index 0000000000..e192ae5560 --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueRecvByteAllocatorHandle.java @@ -0,0 +1,127 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.ChannelConfig; +import io.netty.channel.RecvByteBufAllocator; +import io.netty.util.UncheckedBooleanSupplier; +import io.netty.util.internal.ObjectUtil; + +import static java.lang.Math.max; +import static java.lang.Math.min; + +final class KQueueRecvByteAllocatorHandle implements RecvByteBufAllocator.ExtendedHandle { + private final RecvByteBufAllocator.ExtendedHandle delegate; + private final UncheckedBooleanSupplier defaultMaybeMoreDataSupplier = new UncheckedBooleanSupplier() { + @Override + public boolean get() { + return maybeMoreDataToRead(); + } + }; + private boolean overrideGuess; + private boolean readEOF; + private long numberBytesPending; + + KQueueRecvByteAllocatorHandle(RecvByteBufAllocator.ExtendedHandle handle) { + this.delegate = ObjectUtil.checkNotNull(handle, "handle"); + } + + @Override + public int guess() { + return overrideGuess ? guess0() : delegate.guess(); + } + + @Override + public void reset(ChannelConfig config) { + overrideGuess = ((KQueueChannelConfig) config).getRcvAllocTransportProvidesGuess(); + delegate.reset(config); + } + + @Override + public void incMessagesRead(int numMessages) { + delegate.incMessagesRead(numMessages); + } + + @Override + public ByteBuf allocate(ByteBufAllocator alloc) { + return overrideGuess ? alloc.ioBuffer(guess0()) : delegate.allocate(alloc); + } + + @Override + public void lastBytesRead(int bytes) { + numberBytesPending = bytes < 0 ? 0 : max(0, numberBytesPending - bytes); + delegate.lastBytesRead(bytes); + } + + @Override + public int lastBytesRead() { + return delegate.lastBytesRead(); + } + + @Override + public void attemptedBytesRead(int bytes) { + delegate.attemptedBytesRead(bytes); + } + + @Override + public int attemptedBytesRead() { + return delegate.attemptedBytesRead(); + } + + @Override + public void readComplete() { + delegate.readComplete(); + } + + @Override + public boolean continueReading(UncheckedBooleanSupplier maybeMoreDataSupplier) { + return delegate.continueReading(maybeMoreDataSupplier); + } + + @Override + public boolean continueReading() { + // We must override the supplier which determines if there maybe more data to read. + return delegate.continueReading(defaultMaybeMoreDataSupplier); + } + + void readEOF() { + readEOF = true; + } + + void numberBytesPending(long numberBytesPending) { + this.numberBytesPending = numberBytesPending; + } + + boolean maybeMoreDataToRead() { + /** + * kqueue with EV_CLEAR flag set requires that we read until we consume "data" bytes + * (see kqueue man). However in order to + * respect auto read we supporting reading to stop if auto read is off. If auto read is on we force reading to + * continue to avoid a {@link StackOverflowError} between channelReadComplete and reading from the + * channel. It is expected that the {@link #KQueueSocketChannel} implementations will track if all data was not + * read, and will force a EVFILT_READ ready event. + * + * If EOF has been read we must read until we get an error. + */ + return numberBytesPending != 0 || readEOF; + } + + private int guess0() { + return (int) min(numberBytesPending, Integer.MAX_VALUE); + } +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueServerChannelConfig.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueServerChannelConfig.java new file mode 100644 index 0000000000..7f878dc257 --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueServerChannelConfig.java @@ -0,0 +1,201 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.ChannelException; +import io.netty.channel.ChannelOption; +import io.netty.channel.MessageSizeEstimator; +import io.netty.channel.RecvByteBufAllocator; +import io.netty.channel.WriteBufferWaterMark; +import io.netty.channel.socket.ServerSocketChannelConfig; +import io.netty.util.NetUtil; +import io.netty.util.internal.UnstableApi; + +import java.io.IOException; +import java.util.Map; + +import static io.netty.channel.ChannelOption.SO_BACKLOG; +import static io.netty.channel.ChannelOption.SO_RCVBUF; +import static io.netty.channel.ChannelOption.SO_REUSEADDR; + +@UnstableApi +public class KQueueServerChannelConfig extends KQueueChannelConfig implements ServerSocketChannelConfig { + protected final AbstractKQueueChannel channel; + private volatile int backlog = NetUtil.SOMAXCONN; + + KQueueServerChannelConfig(AbstractKQueueChannel channel) { + super(channel); + this.channel = channel; + } + + @Override + public Map, Object> getOptions() { + return getOptions(super.getOptions(), SO_RCVBUF, SO_REUSEADDR, SO_BACKLOG); + } + + @SuppressWarnings("unchecked") + @Override + public T getOption(ChannelOption option) { + if (option == SO_RCVBUF) { + return (T) Integer.valueOf(getReceiveBufferSize()); + } + if (option == SO_REUSEADDR) { + return (T) Boolean.valueOf(isReuseAddress()); + } + if (option == SO_BACKLOG) { + return (T) Integer.valueOf(getBacklog()); + } + return super.getOption(option); + } + + @Override + public boolean setOption(ChannelOption option, T value) { + validate(option, value); + + if (option == SO_RCVBUF) { + setReceiveBufferSize((Integer) value); + } else if (option == SO_REUSEADDR) { + setReuseAddress((Boolean) value); + } else if (option == SO_BACKLOG) { + setBacklog((Integer) value); + } else { + return super.setOption(option, value); + } + + return true; + } + + public boolean isReuseAddress() { + try { + return channel.socket.isReuseAddress(); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + public KQueueServerChannelConfig setReuseAddress(boolean reuseAddress) { + try { + channel.socket.setReuseAddress(reuseAddress); + return this; + } catch (IOException e) { + throw new ChannelException(e); + } + } + + public int getReceiveBufferSize() { + try { + return channel.socket.getReceiveBufferSize(); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + public KQueueServerChannelConfig setReceiveBufferSize(int receiveBufferSize) { + try { + channel.socket.setReceiveBufferSize(receiveBufferSize); + return this; + } catch (IOException e) { + throw new ChannelException(e); + } + } + + public int getBacklog() { + return backlog; + } + + public KQueueServerChannelConfig setBacklog(int backlog) { + if (backlog < 0) { + throw new IllegalArgumentException("backlog: " + backlog); + } + this.backlog = backlog; + return this; + } + + @Override + public KQueueServerChannelConfig setRcvAllocTransportProvidesGuess(boolean transportProvidesGuess) { + super.setRcvAllocTransportProvidesGuess(transportProvidesGuess); + return this; + } + + @Override + public KQueueServerChannelConfig setPerformancePreferences(int connectionTime, int latency, int bandwidth) { + return this; + } + + @Override + public KQueueServerChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis) { + super.setConnectTimeoutMillis(connectTimeoutMillis); + return this; + } + + @Override + @Deprecated + public KQueueServerChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead) { + super.setMaxMessagesPerRead(maxMessagesPerRead); + return this; + } + + @Override + public KQueueServerChannelConfig setWriteSpinCount(int writeSpinCount) { + super.setWriteSpinCount(writeSpinCount); + return this; + } + + @Override + public KQueueServerChannelConfig setAllocator(ByteBufAllocator allocator) { + super.setAllocator(allocator); + return this; + } + + @Override + public KQueueServerChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator) { + super.setRecvByteBufAllocator(allocator); + return this; + } + + @Override + public KQueueServerChannelConfig setAutoRead(boolean autoRead) { + super.setAutoRead(autoRead); + return this; + } + + @Override + @Deprecated + public KQueueServerChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark) { + super.setWriteBufferHighWaterMark(writeBufferHighWaterMark); + return this; + } + + @Override + @Deprecated + public KQueueServerChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark) { + super.setWriteBufferLowWaterMark(writeBufferLowWaterMark); + return this; + } + + @Override + public KQueueServerChannelConfig setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark) { + super.setWriteBufferWaterMark(writeBufferWaterMark); + return this; + } + + @Override + public KQueueServerChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator) { + super.setMessageSizeEstimator(estimator); + return this; + } +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueServerDomainSocketChannel.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueServerDomainSocketChannel.java new file mode 100644 index 0000000000..5c8a2b34ee --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueServerDomainSocketChannel.java @@ -0,0 +1,96 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.channel.Channel; +import io.netty.channel.unix.DomainSocketAddress; +import io.netty.channel.unix.ServerDomainSocketChannel; +import io.netty.util.internal.UnstableApi; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; + +import java.io.File; +import java.net.SocketAddress; + +import static io.netty.channel.kqueue.BsdSocket.newSocketDomain; + +@UnstableApi +public final class KQueueServerDomainSocketChannel extends AbstractKQueueServerChannel + implements ServerDomainSocketChannel { + private static final InternalLogger logger = InternalLoggerFactory.getInstance( + KQueueServerDomainSocketChannel.class); + + private final KQueueServerChannelConfig config = new KQueueServerChannelConfig(this); + private volatile DomainSocketAddress local; + + public KQueueServerDomainSocketChannel() { + super(newSocketDomain(), false); + } + + KQueueServerDomainSocketChannel(BsdSocket socket, boolean active) { + super(socket, active); + } + + @Override + protected Channel newChildChannel(int fd, byte[] addr, int offset, int len) throws Exception { + return new KQueueDomainSocketChannel(this, new BsdSocket(fd)); + } + + @Override + protected DomainSocketAddress localAddress0() { + return local; + } + + @Override + protected void doBind(SocketAddress localAddress) throws Exception { + socket.bind(localAddress); + socket.listen(config.getBacklog()); + local = (DomainSocketAddress) localAddress; + active = true; + } + + @Override + protected void doClose() throws Exception { + try { + super.doClose(); + } finally { + DomainSocketAddress local = this.local; + if (local != null) { + // Delete the socket file if possible. + File socketFile = new File(local.path()); + boolean success = socketFile.delete(); + if (!success && logger.isDebugEnabled()) { + logger.debug("Failed to delete a domain socket file: {}", local.path()); + } + } + } + } + + @Override + public KQueueServerChannelConfig config() { + return config; + } + + @Override + public DomainSocketAddress remoteAddress() { + return (DomainSocketAddress) super.remoteAddress(); + } + + @Override + public DomainSocketAddress localAddress() { + return (DomainSocketAddress) super.localAddress(); + } +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueServerSocketChannel.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueServerSocketChannel.java new file mode 100644 index 0000000000..22f14435cb --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueServerSocketChannel.java @@ -0,0 +1,87 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.channel.Channel; +import io.netty.channel.EventLoop; +import io.netty.channel.socket.ServerSocketChannel; +import io.netty.util.internal.UnstableApi; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +import static io.netty.channel.kqueue.BsdSocket.newSocketStream; +import static io.netty.channel.unix.NativeInetAddress.address; + +@UnstableApi +public final class KQueueServerSocketChannel extends AbstractKQueueServerChannel implements ServerSocketChannel { + private final KQueueServerSocketChannelConfig config; + private volatile InetSocketAddress local; + + public KQueueServerSocketChannel() { + super(newSocketStream(), false); + config = new KQueueServerSocketChannelConfig(this); + } + + KQueueServerSocketChannel(BsdSocket fd, boolean active) { + super(fd, active); + config = new KQueueServerSocketChannelConfig(this); + // As we create an KQueueServerSocketChannel from a FileDescriptor we should try to obtain the remote and local + // address from it. This is needed as the FileDescriptor may be bound already. + local = fd.localAddress(); + } + + @Override + protected boolean isCompatible(EventLoop loop) { + return loop instanceof KQueueEventLoop; + } + + @Override + protected void doBind(SocketAddress localAddress) throws Exception { + InetSocketAddress addr = (InetSocketAddress) localAddress; + checkResolvable(addr); + socket.bind(addr); + local = socket.localAddress(); + // TODO(scott): tcp fast open here! + socket.listen(config.getBacklog()); + active = true; + } + + @Override + public InetSocketAddress remoteAddress() { + return (InetSocketAddress) super.remoteAddress(); + } + + @Override + public InetSocketAddress localAddress() { + return (InetSocketAddress) super.localAddress(); + } + + @Override + public KQueueServerSocketChannelConfig config() { + return config; + } + + @Override + protected InetSocketAddress localAddress0() { + return local; + } + + @Override + protected Channel newChildChannel(int fd, byte[] address, int offset, int len) throws Exception { + return new KQueueSocketChannel(this, new BsdSocket(fd), address(address, offset, len)); + } +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueServerSocketChannelConfig.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueServerSocketChannelConfig.java new file mode 100644 index 0000000000..dce3e6e71b --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueServerSocketChannelConfig.java @@ -0,0 +1,201 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.ChannelException; +import io.netty.channel.ChannelOption; +import io.netty.channel.MessageSizeEstimator; +import io.netty.channel.RecvByteBufAllocator; +import io.netty.channel.WriteBufferWaterMark; +import io.netty.channel.socket.ServerSocketChannelConfig; +import io.netty.util.internal.UnstableApi; + +import java.io.IOException; +import java.util.Map; + +import static io.netty.channel.kqueue.KQueueChannelOption.SO_ACCEPTFILTER; +import static io.netty.channel.unix.UnixChannelOption.SO_REUSEPORT; + +@UnstableApi +public class KQueueServerSocketChannelConfig extends KQueueServerChannelConfig implements ServerSocketChannelConfig { + KQueueServerSocketChannelConfig(KQueueServerSocketChannel channel) { + super(channel); + + // Use SO_REUSEADDR by default as java.nio does the same. + // + // See https://github.com/netty/netty/issues/2605 + setReuseAddress(true); + } + + @Override + public Map, Object> getOptions() { + return getOptions(super.getOptions(), SO_REUSEPORT, SO_ACCEPTFILTER); + } + + @SuppressWarnings("unchecked") + @Override + public T getOption(ChannelOption option) { + if (option == SO_REUSEPORT) { + return (T) Boolean.valueOf(isReusePort()); + } + if (option == SO_ACCEPTFILTER) { + return (T) getAcceptFilter(); + } + return super.getOption(option); + } + + @Override + public boolean setOption(ChannelOption option, T value) { + validate(option, value); + + if (option == SO_REUSEPORT) { + setReusePort((Boolean) value); + } else if (option == SO_ACCEPTFILTER) { + setAcceptFilter((AcceptFilter) value); + } else { + return super.setOption(option, value); + } + + return true; + } + + public KQueueServerSocketChannelConfig setReusePort(boolean reusePort) { + try { + channel.socket.setReusePort(reusePort); + return this; + } catch (IOException e) { + throw new ChannelException(e); + } + } + + public boolean isReusePort() { + try { + return channel.socket.isReusePort(); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + public KQueueServerSocketChannelConfig setAcceptFilter(AcceptFilter acceptFilter) { + try { + channel.socket.setAcceptFilter(acceptFilter); + return this; + } catch (IOException e) { + throw new ChannelException(e); + } + } + + public AcceptFilter getAcceptFilter() { + try { + return channel.socket.getAcceptFilter(); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public KQueueServerSocketChannelConfig setRcvAllocTransportProvidesGuess(boolean transportProvidesGuess) { + super.setRcvAllocTransportProvidesGuess(transportProvidesGuess); + return this; + } + + @Override + public KQueueServerSocketChannelConfig setReuseAddress(boolean reuseAddress) { + super.setReuseAddress(reuseAddress); + return this; + } + + @Override + public KQueueServerSocketChannelConfig setReceiveBufferSize(int receiveBufferSize) { + super.setReceiveBufferSize(receiveBufferSize); + return this; + } + + @Override + public KQueueServerSocketChannelConfig setPerformancePreferences(int connectionTime, int latency, int bandwidth) { + return this; + } + + @Override + public KQueueServerSocketChannelConfig setBacklog(int backlog) { + super.setBacklog(backlog); + return this; + } + + @Override + public KQueueServerSocketChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis) { + super.setConnectTimeoutMillis(connectTimeoutMillis); + return this; + } + + @Override + @Deprecated + public KQueueServerSocketChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead) { + super.setMaxMessagesPerRead(maxMessagesPerRead); + return this; + } + + @Override + public KQueueServerSocketChannelConfig setWriteSpinCount(int writeSpinCount) { + super.setWriteSpinCount(writeSpinCount); + return this; + } + + @Override + public KQueueServerSocketChannelConfig setAllocator(ByteBufAllocator allocator) { + super.setAllocator(allocator); + return this; + } + + @Override + public KQueueServerSocketChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator) { + super.setRecvByteBufAllocator(allocator); + return this; + } + + @Override + public KQueueServerSocketChannelConfig setAutoRead(boolean autoRead) { + super.setAutoRead(autoRead); + return this; + } + + @Override + @Deprecated + public KQueueServerSocketChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark) { + super.setWriteBufferHighWaterMark(writeBufferHighWaterMark); + return this; + } + + @Override + @Deprecated + public KQueueServerSocketChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark) { + super.setWriteBufferLowWaterMark(writeBufferLowWaterMark); + return this; + } + + @Override + public KQueueServerSocketChannelConfig setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark) { + super.setWriteBufferWaterMark(writeBufferWaterMark); + return this; + } + + @Override + public KQueueServerSocketChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator) { + super.setMessageSizeEstimator(estimator); + return this; + } +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueSocketChannel.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueSocketChannel.java new file mode 100644 index 0000000000..c894a7e1b0 --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueSocketChannel.java @@ -0,0 +1,176 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.channel.Channel; +import io.netty.channel.socket.ServerSocketChannel; +import io.netty.channel.socket.SocketChannel; +import io.netty.util.concurrent.GlobalEventExecutor; +import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.UnstableApi; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.UnknownHostException; +import java.nio.channels.AlreadyConnectedException; +import java.util.concurrent.Executor; + +@UnstableApi +public final class KQueueSocketChannel extends AbstractKQueueStreamChannel implements SocketChannel { + private final KQueueSocketChannelConfig config; + + private volatile InetSocketAddress local; + private volatile InetSocketAddress remote; + private InetSocketAddress requestedRemote; + + public KQueueSocketChannel() { + super(null, BsdSocket.newSocketStream(), false); + config = new KQueueSocketChannelConfig(this); + } + + KQueueSocketChannel(Channel parent, BsdSocket fd, InetSocketAddress remote) { + super(parent, fd, true); + config = new KQueueSocketChannelConfig(this); + // Directly cache the remote and local addresses + // See https://github.com/netty/netty/issues/2359 + this.remote = remote; + local = fd.localAddress(); + } + + @Override + public InetSocketAddress remoteAddress() { + return (InetSocketAddress) super.remoteAddress(); + } + + @Override + public InetSocketAddress localAddress() { + return (InetSocketAddress) super.localAddress(); + } + + @Override + protected SocketAddress localAddress0() { + return local; + } + + @Override + protected SocketAddress remoteAddress0() { + return remote; + } + + @Override + protected void doBind(SocketAddress local) throws Exception { + InetSocketAddress localAddress = (InetSocketAddress) local; + socket.bind(localAddress); + this.local = socket.localAddress(); + } + + @Override + public KQueueSocketChannelConfig config() { + return config; + } + + @Override + public ServerSocketChannel parent() { + return (ServerSocketChannel) super.parent(); + } + + @Override + protected AbstractKQueueUnsafe newUnsafe() { + return new KQueueSocketChannelUnsafe(); + } + + private static InetSocketAddress computeRemoteAddr(InetSocketAddress remoteAddr, InetSocketAddress osRemoteAddr) { + if (osRemoteAddr != null) { + if (PlatformDependent.javaVersion() >= 7) { + try { + // Only try to construct a new InetSocketAddress if we using java >= 7 as getHostString() does not + // exists in earlier releases and so the retrieval of the hostname could block the EventLoop if a + // reverse lookup would be needed. + return new InetSocketAddress(InetAddress.getByAddress(remoteAddr.getHostString(), + osRemoteAddr.getAddress().getAddress()), + osRemoteAddr.getPort()); + } catch (UnknownHostException ignore) { + // Should never happen but fallback to osRemoteAddr anyway. + } + } + return osRemoteAddr; + } + return remoteAddr; + } + + @Override + protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception { + if (localAddress != null) { + checkResolvable((InetSocketAddress) localAddress); + } + InetSocketAddress remoteAddr = (InetSocketAddress) remoteAddress; + checkResolvable(remoteAddr); + + if (remote != null) { + // Check if already connected before trying to connect. This is needed as connect(...) will not return -1 + // and set errno to EISCONN if a previous connect(...) attempt was setting errno to EINPROGRESS and finished + // later. + throw new AlreadyConnectedException(); + } + + boolean connected = super.doConnect(remoteAddress, localAddress); + if (connected) { + remote = computeRemoteAddr(remoteAddr, socket.remoteAddress()); + } else { + // Store for later usage in doFinishConnect() + requestedRemote = remoteAddr; + } + // We always need to set the localAddress even if not connected yet as the bind already took place. + // + // See https://github.com/netty/netty/issues/3463 + local = socket.localAddress(); + return connected; + } + + private final class KQueueSocketChannelUnsafe extends KQueueStreamUnsafe { + @Override + protected Executor prepareToClose() { + try { + // Check isOpen() first as otherwise it will throw a RuntimeException + // when call getSoLinger() as the fd is not valid anymore. + if (isOpen() && config().getSoLinger() > 0) { + // We need to cancel this key of the channel so we may not end up in a eventloop spin + // because we try to read or write until the actual close happens which may be later due + // SO_LINGER handling. + // See https://github.com/netty/netty/issues/4449 + ((KQueueEventLoop) eventLoop()).remove(KQueueSocketChannel.this); + return GlobalEventExecutor.INSTANCE; + } + } catch (Throwable ignore) { + // Ignore the error as the underlying channel may be closed in the meantime and so + // getSoLinger() may produce an exception. In this case we just return null. + // See https://github.com/netty/netty/issues/4449 + } + return null; + } + + @Override + boolean doFinishConnect() throws Exception { + if (super.doFinishConnect()) { + remote = computeRemoteAddr(requestedRemote, socket.remoteAddress()); + requestedRemote = null; + return true; + } + return false; + } + } +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueSocketChannelConfig.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueSocketChannelConfig.java new file mode 100644 index 0000000000..a72d64ded1 --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueSocketChannelConfig.java @@ -0,0 +1,386 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.ChannelException; +import io.netty.channel.ChannelOption; +import io.netty.channel.MessageSizeEstimator; +import io.netty.channel.RecvByteBufAllocator; +import io.netty.channel.WriteBufferWaterMark; +import io.netty.channel.socket.SocketChannelConfig; +import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.UnstableApi; + +import java.io.IOException; +import java.util.Map; + +import static io.netty.channel.ChannelOption.ALLOW_HALF_CLOSURE; +import static io.netty.channel.ChannelOption.IP_TOS; +import static io.netty.channel.ChannelOption.SO_KEEPALIVE; +import static io.netty.channel.ChannelOption.SO_LINGER; +import static io.netty.channel.ChannelOption.SO_RCVBUF; +import static io.netty.channel.ChannelOption.SO_REUSEADDR; +import static io.netty.channel.ChannelOption.SO_SNDBUF; +import static io.netty.channel.ChannelOption.TCP_NODELAY; +import static io.netty.channel.kqueue.KQueueChannelOption.SO_SNDLOWAT; +import static io.netty.channel.kqueue.KQueueChannelOption.TCP_NOPUSH; + +@UnstableApi +public final class KQueueSocketChannelConfig extends KQueueChannelConfig implements SocketChannelConfig { + private final KQueueSocketChannel channel; + private volatile boolean allowHalfClosure; + + KQueueSocketChannelConfig(KQueueSocketChannel channel) { + super(channel); + this.channel = channel; + if (PlatformDependent.canEnableTcpNoDelayByDefault()) { + setTcpNoDelay(true); + } + } + + @Override + public Map, Object> getOptions() { + return getOptions( + super.getOptions(), + SO_RCVBUF, SO_SNDBUF, TCP_NODELAY, SO_KEEPALIVE, SO_REUSEADDR, SO_LINGER, IP_TOS, + ALLOW_HALF_CLOSURE, SO_SNDLOWAT, TCP_NOPUSH); + } + + @SuppressWarnings("unchecked") + @Override + public T getOption(ChannelOption option) { + if (option == SO_RCVBUF) { + return (T) Integer.valueOf(getReceiveBufferSize()); + } + if (option == SO_SNDBUF) { + return (T) Integer.valueOf(getSendBufferSize()); + } + if (option == TCP_NODELAY) { + return (T) Boolean.valueOf(isTcpNoDelay()); + } + if (option == SO_KEEPALIVE) { + return (T) Boolean.valueOf(isKeepAlive()); + } + if (option == SO_REUSEADDR) { + return (T) Boolean.valueOf(isReuseAddress()); + } + if (option == SO_LINGER) { + return (T) Integer.valueOf(getSoLinger()); + } + if (option == IP_TOS) { + return (T) Integer.valueOf(getTrafficClass()); + } + if (option == ALLOW_HALF_CLOSURE) { + return (T) Boolean.valueOf(isAllowHalfClosure()); + } + if (option == SO_SNDLOWAT) { + return (T) Integer.valueOf(getSndLowAt()); + } + if (option == TCP_NOPUSH) { + return (T) Boolean.valueOf(isTcpNoPush()); + } + return super.getOption(option); + } + + @Override + public boolean setOption(ChannelOption option, T value) { + validate(option, value); + + if (option == SO_RCVBUF) { + setReceiveBufferSize((Integer) value); + } else if (option == SO_SNDBUF) { + setSendBufferSize((Integer) value); + } else if (option == TCP_NODELAY) { + setTcpNoDelay((Boolean) value); + } else if (option == SO_KEEPALIVE) { + setKeepAlive((Boolean) value); + } else if (option == SO_REUSEADDR) { + setReuseAddress((Boolean) value); + } else if (option == SO_LINGER) { + setSoLinger((Integer) value); + } else if (option == IP_TOS) { + setTrafficClass((Integer) value); + } else if (option == ALLOW_HALF_CLOSURE) { + setAllowHalfClosure((Boolean) value); + } else if (option == SO_SNDLOWAT) { + setSndLowAt((Integer) value); + } else if (option == TCP_NOPUSH) { + setTcpNoPush((Boolean) value); + } else { + return super.setOption(option, value); + } + + return true; + } + + @Override + public int getReceiveBufferSize() { + try { + return channel.socket.getReceiveBufferSize(); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public int getSendBufferSize() { + try { + return channel.socket.getSendBufferSize(); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public int getSoLinger() { + try { + return channel.socket.getSoLinger(); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public int getTrafficClass() { + try { + return channel.socket.getTrafficClass(); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public boolean isKeepAlive() { + try { + return channel.socket.isKeepAlive(); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public boolean isReuseAddress() { + try { + return channel.socket.isReuseAddress(); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public boolean isTcpNoDelay() { + try { + return channel.socket.isTcpNoDelay(); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + public int getSndLowAt() { + try { + return channel.socket.getSndLowAt(); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + public void setSndLowAt(int sndLowAt) { + try { + channel.socket.setSndLowAt(sndLowAt); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + public boolean isTcpNoPush() { + try { + return channel.socket.isTcpNoPush(); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + public void setTcpNoPush(boolean tcpNoPush) { + try { + channel.socket.setTcpNoPush(tcpNoPush); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public KQueueSocketChannelConfig setKeepAlive(boolean keepAlive) { + try { + channel.socket.setKeepAlive(keepAlive); + return this; + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public KQueueSocketChannelConfig setReceiveBufferSize(int receiveBufferSize) { + try { + channel.socket.setReceiveBufferSize(receiveBufferSize); + return this; + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public KQueueSocketChannelConfig setReuseAddress(boolean reuseAddress) { + try { + channel.socket.setReuseAddress(reuseAddress); + return this; + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public KQueueSocketChannelConfig setSendBufferSize(int sendBufferSize) { + try { + channel.socket.setSendBufferSize(sendBufferSize); + return this; + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public KQueueSocketChannelConfig setSoLinger(int soLinger) { + try { + channel.socket.setSoLinger(soLinger); + return this; + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public KQueueSocketChannelConfig setTcpNoDelay(boolean tcpNoDelay) { + try { + channel.socket.setTcpNoDelay(tcpNoDelay); + return this; + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public KQueueSocketChannelConfig setTrafficClass(int trafficClass) { + try { + channel.socket.setTrafficClass(trafficClass); + return this; + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public boolean isAllowHalfClosure() { + return allowHalfClosure; + } + + @Override + public KQueueSocketChannelConfig setRcvAllocTransportProvidesGuess(boolean transportProvidesGuess) { + super.setRcvAllocTransportProvidesGuess(transportProvidesGuess); + return this; + } + + @Override + public KQueueSocketChannelConfig setPerformancePreferences( + int connectionTime, int latency, int bandwidth) { + return this; + } + + @Override + public KQueueSocketChannelConfig setAllowHalfClosure(boolean allowHalfClosure) { + this.allowHalfClosure = allowHalfClosure; + return this; + } + + @Override + public KQueueSocketChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis) { + super.setConnectTimeoutMillis(connectTimeoutMillis); + return this; + } + + @Override + @Deprecated + public KQueueSocketChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead) { + super.setMaxMessagesPerRead(maxMessagesPerRead); + return this; + } + + @Override + public KQueueSocketChannelConfig setWriteSpinCount(int writeSpinCount) { + super.setWriteSpinCount(writeSpinCount); + return this; + } + + @Override + public KQueueSocketChannelConfig setAllocator(ByteBufAllocator allocator) { + super.setAllocator(allocator); + return this; + } + + @Override + public KQueueSocketChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator) { + super.setRecvByteBufAllocator(allocator); + return this; + } + + @Override + public KQueueSocketChannelConfig setAutoRead(boolean autoRead) { + super.setAutoRead(autoRead); + return this; + } + + @Override + public KQueueSocketChannelConfig setAutoClose(boolean autoClose) { + super.setAutoClose(autoClose); + return this; + } + + @Override + @Deprecated + public KQueueSocketChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark) { + super.setWriteBufferHighWaterMark(writeBufferHighWaterMark); + return this; + } + + @Override + @Deprecated + public KQueueSocketChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark) { + super.setWriteBufferLowWaterMark(writeBufferLowWaterMark); + return this; + } + + @Override + public KQueueSocketChannelConfig setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark) { + super.setWriteBufferWaterMark(writeBufferWaterMark); + return this; + } + + @Override + public KQueueSocketChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator) { + super.setMessageSizeEstimator(estimator); + return this; + } +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueStaticallyReferencedJniMethods.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueStaticallyReferencedJniMethods.java new file mode 100644 index 0000000000..0e8ed698c3 --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/KQueueStaticallyReferencedJniMethods.java @@ -0,0 +1,43 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +/** + * This class is necessary to break the following cyclic dependency: + *
    + *
  1. JNI_OnLoad
  2. + *
  3. JNI Calls FindClass because RegisterNatives (used to register JNI methods) requires a class
  4. + *
  5. FindClass loads the class, but static members variables of that class attempt to call a JNI method which has not + * yet been registered.
  6. + *
  7. java.lang.UnsatisfiedLinkError is thrown because native method has not yet been registered.
  8. + *
+ * Static members which call JNI methods must not be declared in this class! + */ +final class KQueueStaticallyReferencedJniMethods { + private KQueueStaticallyReferencedJniMethods() { } + + static native short evAdd(); + static native short evEnable(); + static native short evDisable(); + static native short evDelete(); + static native short evClear(); + static native short evEOF(); + static native short evError(); + + static native short evfiltRead(); + static native short evfiltWrite(); + static native short evfiltUser(); +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/Native.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/Native.java new file mode 100644 index 0000000000..432a650147 --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/Native.java @@ -0,0 +1,110 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.channel.unix.FileDescriptor; +import io.netty.util.internal.NativeLibraryLoader; +import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.SystemPropertyUtil; + +import java.io.IOException; +import java.util.Locale; + +import static io.netty.channel.kqueue.KQueueStaticallyReferencedJniMethods.evAdd; +import static io.netty.channel.kqueue.KQueueStaticallyReferencedJniMethods.evClear; +import static io.netty.channel.kqueue.KQueueStaticallyReferencedJniMethods.evDelete; +import static io.netty.channel.kqueue.KQueueStaticallyReferencedJniMethods.evDisable; +import static io.netty.channel.kqueue.KQueueStaticallyReferencedJniMethods.evEOF; +import static io.netty.channel.kqueue.KQueueStaticallyReferencedJniMethods.evEnable; +import static io.netty.channel.kqueue.KQueueStaticallyReferencedJniMethods.evError; +import static io.netty.channel.kqueue.KQueueStaticallyReferencedJniMethods.evfiltRead; +import static io.netty.channel.kqueue.KQueueStaticallyReferencedJniMethods.evfiltUser; +import static io.netty.channel.kqueue.KQueueStaticallyReferencedJniMethods.evfiltWrite; +import static io.netty.channel.unix.Errors.newIOException; + +/** + * Navite helper methods + *

Internal usage only! + */ +final class Native { + static { + try { + // First, try calling a side-effect free JNI method to see if the library was already + // loaded by the application. + sizeofKEvent(); + } catch (UnsatisfiedLinkError ignore) { + // The library was not previously loaded, load it now. + loadNativeLibrary(); + } + } + + static final short EV_ADD = evAdd(); + static final short EV_ENABLE = evEnable(); + static final short EV_DISABLE = evDisable(); + static final short EV_DELETE = evDelete(); + static final short EV_CLEAR = evClear(); + static final short EV_ERROR = evError(); + static final short EV_EOF = evEOF(); + + // Commonly used combinations of EV defines + static final short EV_ADD_CLEAR_ENABLE = (short) (EV_ADD | EV_CLEAR | EV_ENABLE); + static final short EV_DELETE_DISABLE = (short) (EV_DELETE | EV_DISABLE); + + static final short EVFILT_READ = evfiltRead(); + static final short EVFILT_WRITE = evfiltWrite(); + static final short EVFILT_USER = evfiltUser(); + + static FileDescriptor newKQueue() { + return new FileDescriptor(kqueueCreate()); + } + + static int keventWait(int kqueueFd, KQueueEventArray changeList, KQueueEventArray eventList, + int tvSec, int tvNsec) throws IOException { + int ready = keventWait(kqueueFd, changeList.memoryAddress(), changeList.size(), + eventList.memoryAddress(), eventList.capacity(), tvSec, tvNsec); + if (ready < 0) { + throw newIOException("kevent", ready); + } + return ready; + } + + private static native int kqueueCreate(); + private static native int keventWait(int kqueueFd, long changeListAddress, int changeListLength, + long eventListAddress, int eventListLength, int tvSec, int tvNsec); + static native int keventTriggerUserEvent(int kqueueFd, int ident); + static native int keventAddUserEvent(int kqueueFd, int ident); + + // kevent related + static native int sizeofKEvent(); + static native int offsetofKEventIdent(); + static native int offsetofKEventFlags(); + static native int offsetofKEventFFlags(); + static native int offsetofKEventFilter(); + static native int offsetofKeventData(); + + private static void loadNativeLibrary() { + String name = SystemPropertyUtil.get("os.name").toLowerCase(Locale.UK).trim(); + if (!name.startsWith("mac") && !name.contains("bsd") && !name.startsWith("darwin")) { + throw new IllegalStateException("Only supported on BSD"); + } + NativeLibraryLoader.load(SystemPropertyUtil.get("io.netty.packagePrefix", "").replace('.', '-') + + "netty-transport-native-kqueue", PlatformDependent.getClassLoader(Native.class)); + } + + private Native() { + // utility + } +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/NativeLongArray.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/NativeLongArray.java new file mode 100644 index 0000000000..d9f00094f1 --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/NativeLongArray.java @@ -0,0 +1,87 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.util.internal.PlatformDependent; + +import static io.netty.channel.unix.Limits.SIZEOF_JLONG; + +final class NativeLongArray { + private long memoryAddress; + private int capacity; + private int size; + + NativeLongArray(int capacity) { + if (capacity < 1) { + throw new IllegalArgumentException("capacity must be >= 1 but was " + capacity); + } + memoryAddress = PlatformDependent.allocateMemory(capacity * SIZEOF_JLONG); + this.capacity = capacity; + } + + void add(long value) { + checkSize(); + PlatformDependent.putLong(memoryOffset(size++), value); + } + + void clear() { + size = 0; + } + + boolean isEmpty() { + return size == 0; + } + + void free() { + PlatformDependent.freeMemory(memoryAddress); + memoryAddress = 0; + } + + long memoryAddress() { + return memoryAddress; + } + + long memoryAddressEnd() { + return memoryOffset(size); + } + + private long memoryOffset(int index) { + return memoryAddress + index * SIZEOF_JLONG; + } + + private void checkSize() { + if (size == capacity) { + realloc(); + } + } + + private void realloc() { + // Double the capacity while it is "sufficiently small", and otherwise increase by 50%. + int newLength = capacity <= 65536 ? capacity << 1 : capacity + capacity >> 1; + long newMemoryAddress = PlatformDependent.reallocateMemory(memoryAddress, newLength * SIZEOF_JLONG); + if (newMemoryAddress == 0) { + throw new OutOfMemoryError("unable to allocate " + newLength + " new bytes! Existing capacity is: " + + capacity); + } + memoryAddress = newMemoryAddress; + capacity = newLength; + } + + @Override + public String toString() { + return "memoryAddress: " + memoryAddress + " capacity: " + capacity + " size: " + size; + } +} diff --git a/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/package-info.java b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/package-info.java new file mode 100644 index 0000000000..ae25514b66 --- /dev/null +++ b/transport-native-kqueue/src/main/java/io/netty/channel/kqueue/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +/** + * BSD specific transport. + */ +@UnstableApi +package io.netty.channel.kqueue; + +import io.netty.util.internal.UnstableApi; diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueAbstractDomainSocketEchoTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueAbstractDomainSocketEchoTest.java new file mode 100644 index 0000000000..bc6f3835a7 --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueAbstractDomainSocketEchoTest.java @@ -0,0 +1,27 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.channel.unix.DomainSocketAddress; +import java.net.SocketAddress; +import java.util.UUID; + +public class KQueueAbstractDomainSocketEchoTest extends KQueueDomainSocketEchoTest { + @Override + protected SocketAddress newSocketAddress() { + return new DomainSocketAddress(System.getProperty("java.io.tmpdir") + UUID.randomUUID()); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueChannelConfigTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueChannelConfigTest.java new file mode 100644 index 0000000000..2bb08aac63 --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueChannelConfigTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.channel.ChannelException; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.fail; + +public class KQueueChannelConfigTest { + @Before + public void before() { + KQueue.ensureAvailability(); + } + + @Test + public void testOptionGetThrowsChannelException() throws Exception { + KQueueSocketChannel channel = new KQueueSocketChannel(); + channel.config().getSoLinger(); + channel.fd().close(); + try { + channel.config().getSoLinger(); + fail(); + } catch (ChannelException e) { + // expected + } + } + + @Test + public void testOptionSetThrowsChannelException() throws Exception { + KQueueSocketChannel channel = new KQueueSocketChannel(); + channel.config().setKeepAlive(true); + channel.fd().close(); + try { + channel.config().setKeepAlive(true); + fail(); + } catch (ChannelException e) { + // expected + } + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDatagramUnicastTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDatagramUnicastTest.java new file mode 100644 index 0000000000..c805e9002e --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDatagramUnicastTest.java @@ -0,0 +1,29 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.DatagramUnicastTest; + +import java.util.List; + +public class KQueueDatagramUnicastTest extends DatagramUnicastTest { + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.datagram(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketEchoTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketEchoTest.java new file mode 100644 index 0000000000..dd77711752 --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketEchoTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; + +import java.net.SocketAddress; +import java.util.List; + +public class KQueueDomainSocketEchoTest extends KQueueSocketEchoTest { + @Override + protected SocketAddress newSocketAddress() { + return KQueueSocketTestPermutation.newSocketAddress(); + } + + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.domainSocket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketFdTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketFdTest.java new file mode 100644 index 0000000000..b99a973e34 --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketFdTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.unix.DomainSocketReadMode; +import io.netty.channel.unix.FileDescriptor; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.AbstractSocketTest; +import org.junit.Assert; +import org.junit.Test; + +import java.net.SocketAddress; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +public class KQueueDomainSocketFdTest extends AbstractSocketTest { + @Override + protected SocketAddress newSocketAddress() { + return KQueueSocketTestPermutation.newSocketAddress(); + } + + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.domainSocket(); + } + + @Test(timeout = 30000) + public void testSendRecvFd() throws Throwable { + run(); + } + + public void testSendRecvFd(ServerBootstrap sb, Bootstrap cb) throws Throwable { + final BlockingQueue queue = new LinkedBlockingQueue(1); + sb.childHandler(new ChannelInboundHandlerAdapter() { + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + // Create new channel and obtain a file descriptor from it. + final KQueueDomainSocketChannel ch = new KQueueDomainSocketChannel(); + + ctx.writeAndFlush(ch.fd()).addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (!future.isSuccess()) { + Throwable cause = future.cause(); + queue.offer(cause); + } + } + }); + } + }); + cb.handler(new ChannelInboundHandlerAdapter() { + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + FileDescriptor fd = (FileDescriptor) msg; + queue.offer(fd); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + queue.add(cause); + ctx.close(); + } + }); + cb.option(KQueueChannelOption.DOMAIN_SOCKET_READ_MODE, + DomainSocketReadMode.FILE_DESCRIPTORS); + Channel sc = sb.bind().sync().channel(); + Channel cc = cb.connect().sync().channel(); + + Object received = queue.take(); + cc.close().sync(); + sc.close().sync(); + + if (received instanceof FileDescriptor) { + FileDescriptor fd = (FileDescriptor) received; + Assert.assertTrue(fd.isOpen()); + fd.close(); + Assert.assertFalse(fd.isOpen()); + Assert.assertNull(queue.poll()); + } else { + throw (Throwable) received; + } + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketFileRegionTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketFileRegionTest.java new file mode 100644 index 0000000000..cb84a05101 --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketFileRegionTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; + +import java.net.SocketAddress; +import java.util.List; + +public class KQueueDomainSocketFileRegionTest extends KQueueSocketFileRegionTest { + @Override + protected SocketAddress newSocketAddress() { + return KQueueSocketTestPermutation.newSocketAddress(); + } + + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.domainSocket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketFixedLengthEchoTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketFixedLengthEchoTest.java new file mode 100644 index 0000000000..e9f935e663 --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketFixedLengthEchoTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketFixedLengthEchoTest; + +import java.net.SocketAddress; +import java.util.List; + +public class KQueueDomainSocketFixedLengthEchoTest extends SocketFixedLengthEchoTest { + + @Override + protected SocketAddress newSocketAddress() { + return KQueueSocketTestPermutation.newSocketAddress(); + } + + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.domainSocket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketGatheringWriteTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketGatheringWriteTest.java new file mode 100644 index 0000000000..4785545cfe --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketGatheringWriteTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketGatheringWriteTest; + +import java.net.SocketAddress; +import java.util.List; + +public class KQueueDomainSocketGatheringWriteTest extends SocketGatheringWriteTest { + + @Override + protected SocketAddress newSocketAddress() { + return KQueueSocketTestPermutation.newSocketAddress(); + } + + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.domainSocket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketObjectEchoTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketObjectEchoTest.java new file mode 100644 index 0000000000..c480f681f3 --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketObjectEchoTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketObjectEchoTest; + +import java.net.SocketAddress; +import java.util.List; + +public class KQueueDomainSocketObjectEchoTest extends SocketObjectEchoTest { + @Override + protected SocketAddress newSocketAddress() { + return KQueueSocketTestPermutation.newSocketAddress(); + } + + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.domainSocket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketSslEchoTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketSslEchoTest.java new file mode 100644 index 0000000000..7e02abe777 --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketSslEchoTest.java @@ -0,0 +1,47 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.handler.ssl.SslContext; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketSslEchoTest; + +import java.net.SocketAddress; +import java.util.List; + +public class KQueueDomainSocketSslEchoTest extends SocketSslEchoTest { + public KQueueDomainSocketSslEchoTest( + SslContext serverCtx, SslContext clientCtx, Renegotiation renegotiation, + boolean serverUsesDelegatedTaskExecutor, boolean clientUsesDelegatedTaskExecutor, + boolean autoRead, boolean useChunkedWriteHandler, boolean useCompositeByteBuf) { + + super(serverCtx, clientCtx, renegotiation, + serverUsesDelegatedTaskExecutor, clientUsesDelegatedTaskExecutor, + autoRead, useChunkedWriteHandler, useCompositeByteBuf); + } + + @Override + protected SocketAddress newSocketAddress() { + return KQueueSocketTestPermutation.newSocketAddress(); + } + + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.domainSocket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketSslGreetingTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketSslGreetingTest.java new file mode 100644 index 0000000000..492021a52b --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketSslGreetingTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.handler.ssl.SslContext; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketSslGreetingTest; + +import java.net.SocketAddress; +import java.util.List; + +public class KQueueDomainSocketSslGreetingTest extends SocketSslGreetingTest { + + public KQueueDomainSocketSslGreetingTest(SslContext serverCtx, SslContext clientCtx) { + super(serverCtx, clientCtx); + } + + @Override + protected SocketAddress newSocketAddress() { + return KQueueSocketTestPermutation.newSocketAddress(); + } + + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.domainSocket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketStartTlsTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketStartTlsTest.java new file mode 100644 index 0000000000..851dd994a0 --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketStartTlsTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.handler.ssl.SslContext; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketStartTlsTest; + +import java.net.SocketAddress; +import java.util.List; + +public class KQueueDomainSocketStartTlsTest extends SocketStartTlsTest { + + public KQueueDomainSocketStartTlsTest(SslContext serverCtx, SslContext clientCtx) { + super(serverCtx, clientCtx); + } + + @Override + protected SocketAddress newSocketAddress() { + return KQueueSocketTestPermutation.newSocketAddress(); + } + + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.domainSocket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketStringEchoTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketStringEchoTest.java new file mode 100644 index 0000000000..7c80788c0a --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueDomainSocketStringEchoTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketStringEchoTest; + +import java.net.SocketAddress; +import java.util.List; + +public class KQueueDomainSocketStringEchoTest extends SocketStringEchoTest { + @Override + protected SocketAddress newSocketAddress() { + return KQueueSocketTestPermutation.newSocketAddress(); + } + + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.domainSocket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueETSocketAutoReadTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueETSocketAutoReadTest.java new file mode 100644 index 0000000000..27dd39296b --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueETSocketAutoReadTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketAutoReadTest; + +import java.util.List; + +public class KQueueETSocketAutoReadTest extends SocketAutoReadTest { + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.socket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueETSocketExceptionHandlingTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueETSocketExceptionHandlingTest.java new file mode 100644 index 0000000000..e65bcdd3a2 --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueETSocketExceptionHandlingTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketExceptionHandlingTest; + +import java.util.List; + +public class KQueueETSocketExceptionHandlingTest extends SocketExceptionHandlingTest { + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.socket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueETSocketHalfClosedTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueETSocketHalfClosedTest.java new file mode 100644 index 0000000000..071b5f664c --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueETSocketHalfClosedTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2017 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketHalfClosedTest; + +import java.util.List; + +public class KQueueETSocketHalfClosedTest extends SocketHalfClosedTest { + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.socket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueETSocketReadPendingTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueETSocketReadPendingTest.java new file mode 100644 index 0000000000..628084c7cb --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueETSocketReadPendingTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketReadPendingTest; + +import java.util.List; + +public class KQueueETSocketReadPendingTest extends SocketReadPendingTest { + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.socket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueRcvAllocatorOverrideSocketSslEchoTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueRcvAllocatorOverrideSocketSslEchoTest.java new file mode 100644 index 0000000000..5188837c32 --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueRcvAllocatorOverrideSocketSslEchoTest.java @@ -0,0 +1,41 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.handler.ssl.SslContext; + +public class KQueueRcvAllocatorOverrideSocketSslEchoTest extends KQueueSocketSslEchoTest { + public KQueueRcvAllocatorOverrideSocketSslEchoTest( + SslContext serverCtx, SslContext clientCtx, Renegotiation renegotiation, + boolean serverUsesDelegatedTaskExecutor, boolean clientUsesDelegatedTaskExecutor, + boolean autoRead, boolean useChunkedWriteHandler, boolean useCompositeByteBuf) { + + super(serverCtx, clientCtx, renegotiation, + serverUsesDelegatedTaskExecutor, clientUsesDelegatedTaskExecutor, + autoRead, useChunkedWriteHandler, useCompositeByteBuf); + } + + @Override + protected void configure(ServerBootstrap bootstrap, Bootstrap bootstrap2, ByteBufAllocator allocator) { + super.configure(bootstrap, bootstrap2, allocator); + bootstrap.option(KQueueChannelOption.RCV_ALLOC_TRANSPORT_PROVIDES_GUESS, true); + bootstrap.childOption(KQueueChannelOption.RCV_ALLOC_TRANSPORT_PROVIDES_GUESS, true); + bootstrap2.option(KQueueChannelOption.RCV_ALLOC_TRANSPORT_PROVIDES_GUESS, true); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueServerSocketChannelConfigTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueServerSocketChannelConfigTest.java new file mode 100644 index 0000000000..39bb515ca0 --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueServerSocketChannelConfigTest.java @@ -0,0 +1,82 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.EventLoopGroup; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.net.InetSocketAddress; + +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeThat; + +public class KQueueServerSocketChannelConfigTest { + + private static EventLoopGroup group; + private static KQueueServerSocketChannel ch; + + @BeforeClass + public static void before() { + group = new KQueueEventLoopGroup(1); + ServerBootstrap bootstrap = new ServerBootstrap(); + ch = (KQueueServerSocketChannel) bootstrap.group(group) + .channel(KQueueServerSocketChannel.class) + .childHandler(new ChannelInboundHandlerAdapter()) + .bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); + } + + @AfterClass + public static void after() { + try { + ch.close().syncUninterruptibly(); + } finally { + group.shutdownGracefully(); + } + } + + @Test + public void testReusePort() { + ch.config().setReusePort(false); + assertFalse(ch.config().isReusePort()); + ch.config().setReusePort(true); + assertTrue(ch.config().isReusePort()); + } + + @Test + public void testAcceptFilter() { + AcceptFilter currentFilter = ch.config().getAcceptFilter(); + // Not all platforms support this option (e.g. MacOS doesn't) so test if we support the option first. + assumeThat(currentFilter, not(AcceptFilter.PLATFORM_UNSUPPORTED)); + + AcceptFilter af = new AcceptFilter("test", "foo"); + ch.config().setAcceptFilter(af); + assertEquals(af, ch.config().getAcceptFilter()); + } + + @Test + public void testOptionsDoesNotThrow() { + // If there are some options that are not fully supported they shouldn't throw but instead return some "default" + // object. + assertFalse(ch.config().getOptions().isEmpty()); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketChannelConfigTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketChannelConfigTest.java new file mode 100644 index 0000000000..aafe1aa942 --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketChannelConfigTest.java @@ -0,0 +1,125 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelException; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.EventLoopGroup; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.net.InetSocketAddress; +import java.nio.channels.ClosedChannelException; +import java.util.Random; + +import static io.netty.channel.kqueue.BsdSocket.BSD_SND_LOW_AT_MAX; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeNoException; + +public class KQueueSocketChannelConfigTest { + + private static EventLoopGroup group; + private static KQueueSocketChannel ch; + private static Random rand; + + @BeforeClass + public static void beforeClass() { + rand = new Random(); + group = new KQueueEventLoopGroup(1); + } + + @AfterClass + public static void afterClass() { + group.shutdownGracefully(); + } + + @Before + public void setup() { + Bootstrap bootstrap = new Bootstrap(); + ch = (KQueueSocketChannel) bootstrap.group(group) + .channel(KQueueSocketChannel.class) + .handler(new ChannelInboundHandlerAdapter()) + .bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); + } + + @After + public void teardown() { + ch.close().syncUninterruptibly(); + } + + @Test + public void testRandomSndLowAt() { + final int expected = Math.min(BSD_SND_LOW_AT_MAX, Math.abs(rand.nextInt())); + final int actual; + try { + ch.config().setSndLowAt(expected); + actual = ch.config().getSndLowAt(); + } catch (RuntimeException e) { + assumeNoException(e); + return; // Needed to prevent compile error for final variables to be used below + } + assertEquals(expected, actual); + } + + @Test + public void testInvalidHighSndLowAt() { + try { + ch.config().setSndLowAt(Integer.MIN_VALUE); + } catch (ChannelException e) { + return; + } catch (RuntimeException e) { + assumeNoException(e); + } + fail(); + } + + @Test + public void testTcpNoPush() { + ch.config().setTcpNoPush(false); + assertFalse(ch.config().isTcpNoPush()); + ch.config().setTcpNoPush(true); + assertTrue(ch.config().isTcpNoPush()); + } + + @Test + public void testSetOptionWhenClosed() { + ch.close().syncUninterruptibly(); + try { + ch.config().setSoLinger(0); + fail(); + } catch (ChannelException e) { + assertTrue(e.getCause() instanceof ClosedChannelException); + } + } + + @Test + public void testGetOptionWhenClosed() { + ch.close().syncUninterruptibly(); + try { + ch.config().getSoLinger(); + fail(); + } catch (ChannelException e) { + assertTrue(e.getCause() instanceof ClosedChannelException); + } + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketChannelNotYetConnectedTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketChannelNotYetConnectedTest.java new file mode 100644 index 0000000000..35ef4e919e --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketChannelNotYetConnectedTest.java @@ -0,0 +1,29 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketChannelNotYetConnectedTest; + +import java.util.List; + +public class KQueueSocketChannelNotYetConnectedTest extends SocketChannelNotYetConnectedTest { + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.clientSocket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketConnectTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketConnectTest.java new file mode 100644 index 0000000000..8dcc56133b --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketConnectTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketConnectTest; + +import java.util.List; + +public class KQueueSocketConnectTest extends SocketConnectTest { + + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.socket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketConnectionAttemptTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketConnectionAttemptTest.java new file mode 100644 index 0000000000..824c47cb7f --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketConnectionAttemptTest.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketConnectionAttemptTest; +import org.junit.Test; + +import java.net.InetSocketAddress; +import java.util.List; + +public class KQueueSocketConnectionAttemptTest extends SocketConnectionAttemptTest { + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.clientSocket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketEchoTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketEchoTest.java new file mode 100644 index 0000000000..d85f1e428b --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketEchoTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketEchoTest; + +import java.util.List; + +public class KQueueSocketEchoTest extends SocketEchoTest { + + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.socket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketFileRegionTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketFileRegionTest.java new file mode 100644 index 0000000000..fcfb7071a0 --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketFileRegionTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketFileRegionTest; + +import java.util.List; + +public class KQueueSocketFileRegionTest extends SocketFileRegionTest { + + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.socket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketFixedLengthEchoTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketFixedLengthEchoTest.java new file mode 100644 index 0000000000..67c5115d7a --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketFixedLengthEchoTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketFixedLengthEchoTest; + +import java.util.List; + +public class KQueueSocketFixedLengthEchoTest extends SocketFixedLengthEchoTest { + + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.socket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketGatheringWriteTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketGatheringWriteTest.java new file mode 100644 index 0000000000..ec4f528b26 --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketGatheringWriteTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketGatheringWriteTest; + +import java.util.List; + +public class KQueueSocketGatheringWriteTest extends SocketGatheringWriteTest { + + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.socket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketMultipleConnectTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketMultipleConnectTest.java new file mode 100644 index 0000000000..fb66e5f2d1 --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketMultipleConnectTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketMultipleConnectTest; + +import java.util.ArrayList; +import java.util.List; + +public class KQueueSocketMultipleConnectTest extends SocketMultipleConnectTest { + @Override + protected List> newFactories() { + List> factories + = new ArrayList>(); + for (TestsuitePermutation.BootstrapComboFactory comboFactory + : KQueueSocketTestPermutation.INSTANCE.socket()) { + EventLoopGroup group = comboFactory.newClientInstance().config().group(); + if (group instanceof NioEventLoopGroup || group instanceof KQueueEventLoopGroup) { + factories.add(comboFactory); + } + } + return factories; + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketObjectEchoTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketObjectEchoTest.java new file mode 100644 index 0000000000..115268a565 --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketObjectEchoTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketObjectEchoTest; + +import java.util.List; + +public class KQueueSocketObjectEchoTest extends SocketObjectEchoTest { + + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.socket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketRstTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketRstTest.java new file mode 100644 index 0000000000..638a913452 --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketRstTest.java @@ -0,0 +1,49 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.unix.Errors; +import io.netty.channel.unix.Errors.NativeIoException; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketRstTest; + +import java.io.IOException; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class KQueueSocketRstTest extends SocketRstTest { + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.socket(); + } + + @Override + protected void assertRstOnCloseException(IOException cause, Channel clientChannel) { + if (!AbstractKQueueChannel.class.isInstance(clientChannel)) { + super.assertRstOnCloseException(cause, clientChannel); + return; + } + + assertTrue("actual [type, message]: [" + cause.getClass() + ", " + cause.getMessage() + "]", + cause instanceof NativeIoException); + assertEquals(Errors.ERRNO_ECONNRESET_NEGATIVE, ((NativeIoException) cause).expectedErr()); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketShutdownOutputByPeerTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketShutdownOutputByPeerTest.java new file mode 100644 index 0000000000..ca2084bd1e --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketShutdownOutputByPeerTest.java @@ -0,0 +1,29 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketShutdownOutputByPeerTest; + +import java.util.List; + +public class KQueueSocketShutdownOutputByPeerTest extends SocketShutdownOutputByPeerTest { + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.serverSocket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketShutdownOutputBySelfTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketShutdownOutputBySelfTest.java new file mode 100644 index 0000000000..915584063a --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketShutdownOutputBySelfTest.java @@ -0,0 +1,29 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketShutdownOutputBySelfTest; + +import java.util.List; + +public class KQueueSocketShutdownOutputBySelfTest extends SocketShutdownOutputBySelfTest { + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.clientSocket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketSslEchoTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketSslEchoTest.java new file mode 100644 index 0000000000..3ae1983f79 --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketSslEchoTest.java @@ -0,0 +1,41 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.handler.ssl.SslContext; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketSslEchoTest; + +import java.util.List; + +public class KQueueSocketSslEchoTest extends SocketSslEchoTest { + public KQueueSocketSslEchoTest( + SslContext serverCtx, SslContext clientCtx, Renegotiation renegotiation, + boolean serverUsesDelegatedTaskExecutor, boolean clientUsesDelegatedTaskExecutor, + boolean autoRead, boolean useChunkedWriteHandler, boolean useCompositeByteBuf) { + + super(serverCtx, clientCtx, renegotiation, + serverUsesDelegatedTaskExecutor, clientUsesDelegatedTaskExecutor, + autoRead, useChunkedWriteHandler, useCompositeByteBuf); + } + + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.socket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketSslGreetingTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketSslGreetingTest.java new file mode 100644 index 0000000000..9242fc388f --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketSslGreetingTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.handler.ssl.SslContext; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketSslGreetingTest; + +import java.util.List; + +public class KQueueSocketSslGreetingTest extends SocketSslGreetingTest { + + public KQueueSocketSslGreetingTest(SslContext serverCtx, SslContext clientCtx) { + super(serverCtx, clientCtx); + } + + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.socket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketStartTlsTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketStartTlsTest.java new file mode 100644 index 0000000000..326579e03e --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketStartTlsTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.handler.ssl.SslContext; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketStartTlsTest; + +import java.util.List; + +public class KQueueSocketStartTlsTest extends SocketStartTlsTest { + + public KQueueSocketStartTlsTest(SslContext serverCtx, SslContext clientCtx) { + super(serverCtx, clientCtx); + } + + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.socket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketStringEchoTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketStringEchoTest.java new file mode 100644 index 0000000000..c31adad077 --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketStringEchoTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketStringEchoTest; + +import java.util.List; + +public class KQueueSocketStringEchoTest extends SocketStringEchoTest { + + @Override + protected List> newFactories() { + return KQueueSocketTestPermutation.INSTANCE.socket(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketTest.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketTest.java new file mode 100644 index 0000000000..1634c27c11 --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.channel.unix.DomainSocketAddress; +import io.netty.channel.unix.PeerCredentials; +import io.netty.channel.unix.Socket; +import io.netty.channel.unix.tests.SocketTest; +import io.netty.channel.unix.tests.UnixTestUtils; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +public class KQueueSocketTest extends SocketTest { + @BeforeClass + public static void loadJNI() { + KQueue.isAvailable(); + } + + @Test + public void testPeerCreds() throws IOException { + BsdSocket s1 = BsdSocket.newSocketDomain(); + BsdSocket s2 = BsdSocket.newSocketDomain(); + + try { + DomainSocketAddress dsa = UnixTestUtils.newSocketAddress(); + s1.bind(dsa); + s1.listen(1); + + assertTrue(s2.connect(dsa)); + byte [] addr = new byte[64]; + s1.accept(addr); + PeerCredentials pc = s1.getPeerCredentials(); + assertNotEquals(pc.uid(), -1); + } finally { + s1.close(); + s2.close(); + } + } + + @Override + protected BsdSocket newSocket() { + return BsdSocket.newSocketStream(); + } +} diff --git a/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketTestPermutation.java b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketTestPermutation.java new file mode 100644 index 0000000000..a4f945decd --- /dev/null +++ b/transport-native-kqueue/src/test/java/io/netty/channel/kqueue/KQueueSocketTestPermutation.java @@ -0,0 +1,172 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.kqueue; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFactory; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.socket.InternetProtocolFamily; +import io.netty.channel.socket.nio.NioDatagramChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.channel.unix.DomainSocketAddress; +import io.netty.channel.unix.Socket; +import io.netty.channel.unix.tests.UnixTestUtils; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.TestsuitePermutation.BootstrapFactory; +import io.netty.testsuite.transport.socket.SocketTestPermutation; +import io.netty.util.concurrent.DefaultThreadFactory; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +class KQueueSocketTestPermutation extends SocketTestPermutation { + + static final KQueueSocketTestPermutation INSTANCE = new KQueueSocketTestPermutation(); + + static final EventLoopGroup KQUEUE_BOSS_GROUP = + new KQueueEventLoopGroup(BOSSES, new DefaultThreadFactory("testsuite-KQueue-boss", true)); + static final EventLoopGroup KQUEUE_WORKER_GROUP = + new KQueueEventLoopGroup(WORKERS, new DefaultThreadFactory("testsuite-KQueue-worker", true)); + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(KQueueSocketTestPermutation.class); + + @Override + public List> socket() { + + List> list = + combo(serverSocket(), clientSocket()); + + list.remove(list.size() - 1); // Exclude NIO x NIO test + + return list; + } + + @SuppressWarnings("unchecked") + @Override + public List> serverSocket() { + List> toReturn = new ArrayList>(); + toReturn.add(new BootstrapFactory() { + @Override + public ServerBootstrap newInstance() { + return new ServerBootstrap().group(KQUEUE_BOSS_GROUP, KQUEUE_WORKER_GROUP) + .channel(KQueueServerSocketChannel.class); + } + }); + + toReturn.add(new BootstrapFactory() { + @Override + public ServerBootstrap newInstance() { + return new ServerBootstrap().group(nioBossGroup, nioWorkerGroup) + .channel(NioServerSocketChannel.class); + } + }); + + return toReturn; + } + + @SuppressWarnings("unchecked") + @Override + public List> clientSocket() { + return Arrays.asList( + new BootstrapFactory() { + @Override + public Bootstrap newInstance() { + return new Bootstrap().group(KQUEUE_WORKER_GROUP).channel(KQueueSocketChannel.class); + } + }, + new BootstrapFactory() { + @Override + public Bootstrap newInstance() { + return new Bootstrap().group(nioWorkerGroup).channel(NioSocketChannel.class); + } + } + ); + } + + @Override + public List> datagram() { + // Make the list of Bootstrap factories. + @SuppressWarnings("unchecked") + List> bfs = Arrays.asList( + new BootstrapFactory() { + @Override + public Bootstrap newInstance() { + return new Bootstrap().group(nioWorkerGroup).channelFactory(new ChannelFactory() { + @Override + public Channel newChannel() { + return new NioDatagramChannel(InternetProtocolFamily.IPv4); + } + + @Override + public String toString() { + return NioDatagramChannel.class.getSimpleName() + ".class"; + } + }); + } + }, + new BootstrapFactory() { + @Override + public Bootstrap newInstance() { + return new Bootstrap().group(KQUEUE_WORKER_GROUP).channel(KQueueDatagramChannel.class); + } + } + ); + return combo(bfs, bfs); + } + + public List> domainSocket() { + + List> list = + combo(serverDomainSocket(), clientDomainSocket()); + return list; + } + + public List> serverDomainSocket() { + return Collections.>singletonList( + new BootstrapFactory() { + @Override + public ServerBootstrap newInstance() { + return new ServerBootstrap().group(KQUEUE_BOSS_GROUP, KQUEUE_WORKER_GROUP) + .channel(KQueueServerDomainSocketChannel.class); + } + } + ); + } + + public List> clientDomainSocket() { + return Collections.>singletonList( + new BootstrapFactory() { + @Override + public Bootstrap newInstance() { + return new Bootstrap().group(KQUEUE_WORKER_GROUP).channel(KQueueDomainSocketChannel.class); + } + } + ); + } + + public static DomainSocketAddress newSocketAddress() { + return UnixTestUtils.newSocketAddress(); + } +} diff --git a/transport-native-unix-common-tests/pom.xml b/transport-native-unix-common-tests/pom.xml new file mode 100644 index 0000000000..3b527170dc --- /dev/null +++ b/transport-native-unix-common-tests/pom.xml @@ -0,0 +1,49 @@ + + + + 4.0.0 + + io.netty + netty-parent + 4.1.11.Final-SNAPSHOT + + netty-transport-native-unix-common-tests + + Netty/Transport/Native/Unix/Common/Tests + jar + + Tests for the static library which contains common unix utilities. + + + + + true + + + + + io.netty + netty-transport-native-unix-common + ${project.version} + + + junit + junit + compile + + + diff --git a/transport-native-epoll/src/test/java/io/netty/channel/unix/SocketTest.java b/transport-native-unix-common-tests/src/main/java/io/netty/channel/unix/tests/SocketTest.java similarity index 64% rename from transport-native-epoll/src/test/java/io/netty/channel/unix/SocketTest.java rename to transport-native-unix-common-tests/src/main/java/io/netty/channel/unix/tests/SocketTest.java index c8f98f0f93..b700bc4e4d 100644 --- a/transport-native-epoll/src/test/java/io/netty/channel/unix/SocketTest.java +++ b/transport-native-unix-common-tests/src/main/java/io/netty/channel/unix/tests/SocketTest.java @@ -13,29 +13,27 @@ * License for the specific language governing permissions and limitations * under the License. */ -package io.netty.channel.unix; +package io.netty.channel.unix.tests; -import io.netty.channel.epoll.Epoll; -import java.io.File; +import io.netty.channel.unix.Socket; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.IOException; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; -public class SocketTest { +public abstract class SocketTest { + protected T socket; - static { - Epoll.ensureAvailability(); - } - - private Socket socket; + protected abstract T newSocket(); @Before public void setup() { - socket = Socket.newSocketStream(); + socket = newSocket(); } @After @@ -50,13 +48,6 @@ public class SocketTest { assertTrue(socket.isKeepAlive()); } - @Test - public void testTcpCork() throws Exception { - assertFalse(socket.isTcpCork()); - socket.setTcpCork(true); - assertTrue(socket.isTcpCork()); - } - @Test public void testTcpNoDelay() throws Exception { assertFalse(socket.isTcpNoDelay()); @@ -97,30 +88,4 @@ public class SocketTest { socket.close(); socket.close(); } - - @Test - public void testPeerCreds() throws IOException { - Socket s1 = Socket.newSocketDomain(); - Socket s2 = Socket.newSocketDomain(); - File domainSocketFile = null; - - try { - domainSocketFile = File.createTempFile("netty-test", "sckt"); - DomainSocketAddress dsa = new DomainSocketAddress(domainSocketFile); - s1.bind(dsa); - s1.listen(1); - - assertTrue(s2.connect(dsa)); - byte [] addr = new byte[64]; - s1.accept(addr); - PeerCredentials pc = s1.getPeerCredentials(); - assertNotEquals(pc.uid(), -1); - } finally { - s1.close(); - s2.close(); - if (domainSocketFile != null) { - domainSocketFile.delete(); - } - } - } } diff --git a/transport-native-unix-common-tests/src/main/java/io/netty/channel/unix/tests/UnixTestUtils.java b/transport-native-unix-common-tests/src/main/java/io/netty/channel/unix/tests/UnixTestUtils.java new file mode 100644 index 0000000000..e4ebcb4f42 --- /dev/null +++ b/transport-native-unix-common-tests/src/main/java/io/netty/channel/unix/tests/UnixTestUtils.java @@ -0,0 +1,41 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.unix.tests; + +import io.netty.channel.unix.DomainSocketAddress; +import io.netty.channel.unix.Socket; + +import java.io.File; +import java.io.IOException; + +public final class UnixTestUtils { + public static DomainSocketAddress newSocketAddress() { + try { + File file; + do { + file = File.createTempFile("NETTY", "UDS"); + if (!file.delete()) { + throw new IOException("failed to delete: " + file); + } + } while (file.getAbsolutePath().length() > Socket.UDS_SUN_PATH_SIZE); + return new DomainSocketAddress(file); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + private UnixTestUtils() { } +} diff --git a/transport-native-unix-common-tests/src/main/java/io/netty/channel/unix/tests/package-info.java b/transport-native-unix-common-tests/src/main/java/io/netty/channel/unix/tests/package-info.java new file mode 100644 index 0000000000..b2225fd482 --- /dev/null +++ b/transport-native-unix-common-tests/src/main/java/io/netty/channel/unix/tests/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +/** + * Unix specific transport tests. + */ +package io.netty.channel.unix.tests; diff --git a/transport-native-unix-common/Makefile b/transport-native-unix-common/Makefile new file mode 100644 index 0000000000..3e75742308 --- /dev/null +++ b/transport-native-unix-common/Makefile @@ -0,0 +1,50 @@ +# Copyright 2016 The Netty Project +# +# The Netty Project licenses this file to you under the Apache License, +# version 2.0 (the "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +## GNU Makefile designed to build a static library which can be shared across multiple architectures. + +## Input environment: +# CC - compiler (gcc or clang) +# AR - archiver (ar) +# JNI_PLATFORM - "linux" for linux and "darwin" for mac. +# LIB_DIR - where the static library will be built in +# OBJ_DIR - where the obj files will be built in (defaults to LIB_DIR) +# LIB_NAME - the name of the native library + +SRC_DIR = src/main/c +JNI_INCLUDE_DIR = $(JAVA_HOME)/include +JNI_INCLUDES = -I$(JNI_INCLUDE_DIR) -I$(JNI_INCLUDE_DIR)/$(JNI_PLATFORM) +LIB = $(LIB_DIR)/$(LIB_NAME).a + +CFLAGS += $(JNI_INCLUDES) + +SRCS = $(wildcard $(SRC_DIR)/*.c) +OBJS = $(SRCS:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o) + +all: $(LIB) + +$(LIB): $(OBJS) + mkdir -p $(LIB_DIR) + $(AR) rcs $(LIB) $^ + +$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c + mkdir -p $(OBJ_DIR) + $(CC) -o $@ -c $< $(CFLAGS) + +clean: + rm -rf $(LIB_DIR) $(OBJ_DIR) + +## Debug support +# use make print-VARIABLE name to see the value +print-% : ; @echo $* = $($*) diff --git a/transport-native-unix-common/pom.xml b/transport-native-unix-common/pom.xml new file mode 100644 index 0000000000..c26b70105f --- /dev/null +++ b/transport-native-unix-common/pom.xml @@ -0,0 +1,168 @@ + + + + 4.0.0 + + io.netty + netty-parent + 4.1.11.Final-SNAPSHOT + + netty-transport-native-unix-common + + Netty/Transport/Native/Unix/Common + jar + + Static library which contains common unix utilities. + + + + make + gcc + ar + libnetty-unix-common + ${project.basedir}/src/main/c + ${project.build.directory}/native-jar-work + ${project.build.directory}/native-objs-only + ${project.build.directory}/native-lib-only + ${project.build.directory}/${project.build.finalName}.jar + ${project.build.directory}/${project.build.finalName}-${jni.classifier}.jar + + + + + mac + + + mac + + + + clang + darwin + + + + linux + + + linux + + + + linux + + + + freebsd + + + unix + freebsd + + + + clang + gmake + freebsd + + + + openbsd + + + unix + openbsd + + + + clang + gmake + openbsd + + + + + + + io.netty + netty-common + ${project.version} + + + io.netty + netty-transport + ${project.version} + + + + + + + maven-antrun-plugin + + + + native-jar + package + + run + + + + + + + + + + + + + + + + + + + + + + build-native-lib + generate-sources + + run + + + + + + + + + + + + + + + + + + + + + diff --git a/transport-native-epoll/src/main/c/netty_unix_errors.c b/transport-native-unix-common/src/main/c/netty_unix_errors.c similarity index 100% rename from transport-native-epoll/src/main/c/netty_unix_errors.c rename to transport-native-unix-common/src/main/c/netty_unix_errors.c diff --git a/transport-native-epoll/src/main/c/netty_unix_errors.h b/transport-native-unix-common/src/main/c/netty_unix_errors.h similarity index 100% rename from transport-native-epoll/src/main/c/netty_unix_errors.h rename to transport-native-unix-common/src/main/c/netty_unix_errors.h diff --git a/transport-native-epoll/src/main/c/netty_unix_filedescriptor.c b/transport-native-unix-common/src/main/c/netty_unix_filedescriptor.c similarity index 99% rename from transport-native-epoll/src/main/c/netty_unix_filedescriptor.c rename to transport-native-unix-common/src/main/c/netty_unix_filedescriptor.c index 510e1da5db..8c9fb5bc4e 100644 --- a/transport-native-epoll/src/main/c/netty_unix_filedescriptor.c +++ b/transport-native-unix-common/src/main/c/netty_unix_filedescriptor.c @@ -30,7 +30,7 @@ static jfieldID posFieldId = NULL; static jfieldID limitFieldId = NULL; // Optional external methods -extern int pipe2(int pipefd[2], int flags) __attribute__((weak)); +extern int pipe2(int pipefd[2], int flags) __attribute__((weak)) __attribute__((weak_import)); static jint _write(JNIEnv* env, jclass clazz, jint fd, void* buffer, jint pos, jint limit) { ssize_t res; diff --git a/transport-native-epoll/src/main/c/netty_unix_filedescriptor.h b/transport-native-unix-common/src/main/c/netty_unix_filedescriptor.h similarity index 100% rename from transport-native-epoll/src/main/c/netty_unix_filedescriptor.h rename to transport-native-unix-common/src/main/c/netty_unix_filedescriptor.h diff --git a/transport-native-unix-common/src/main/c/netty_unix_limits.c b/transport-native-unix-common/src/main/c/netty_unix_limits.c new file mode 100644 index 0000000000..4b09e8f505 --- /dev/null +++ b/transport-native-unix-common/src/main/c/netty_unix_limits.c @@ -0,0 +1,81 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +#include +#include +#include +#include +#include "netty_unix_limits.h" +#include "netty_unix_util.h" + +// Define IOV_MAX if not found to limit the iov size on writev calls +// See https://github.com/netty/netty/issues/2647 +#ifndef IOV_MAX +#define IOV_MAX 1024 +#endif /* IOV_MAX */ + +// Define UIO_MAXIOV if not found +#ifndef UIO_MAXIOV +#define UIO_MAXIOV 1024 +#endif /* UIO_MAXIOV */ + +// JNI Registered Methods Begin +static jlong netty_unix_limits_ssizeMax(JNIEnv* env, jclass clazz) { + return SSIZE_MAX; +} + +static jint netty_unix_limits_iovMax(JNIEnv* env, jclass clazz) { + return IOV_MAX; +} + +static jint netty_unix_limits_uioMaxIov(JNIEnv* env, jclass clazz) { + return UIO_MAXIOV; +} + +static jint netty_unix_limits_sizeOfjlong(JNIEnv* env, jclass clazz) { + return sizeof(jlong); +} + +static jint netty_unix_limits_udsSunPathSize(JNIEnv* env, jclass clazz) { + struct sockaddr_un udsAddr; + return sizeof(udsAddr.sun_path) / sizeof(udsAddr.sun_path[0]); +} +// JNI Registered Methods End + +// JNI Method Registration Table Begin +static const JNINativeMethod statically_referenced_fixed_method_table[] = { + { "ssizeMax", "()J", (void *) netty_unix_limits_ssizeMax }, + { "iovMax", "()I", (void *) netty_unix_limits_iovMax }, + { "uioMaxIov", "()I", (void *) netty_unix_limits_uioMaxIov }, + { "sizeOfjlong", "()I", (void *) netty_unix_limits_sizeOfjlong }, + { "udsSunPathSize", "()I", (void *) netty_unix_limits_udsSunPathSize } +}; +static const jint statically_referenced_fixed_method_table_size = sizeof(statically_referenced_fixed_method_table) / sizeof(statically_referenced_fixed_method_table[0]); +// JNI Method Registration Table End + +jint netty_unix_limits_JNI_OnLoad(JNIEnv* env, const char* packagePrefix) { + // We must register the statically referenced methods first! + if (netty_unix_util_register_natives(env, + packagePrefix, + "io/netty/channel/unix/LimitsStaticallyReferencedJniMethods", + statically_referenced_fixed_method_table, + statically_referenced_fixed_method_table_size) != 0) { + return JNI_ERR; + } + + return JNI_VERSION_1_6; +} + +void netty_unix_limits_JNI_OnUnLoad(JNIEnv* env) { } diff --git a/transport-native-unix-common/src/main/c/netty_unix_limits.h b/transport-native-unix-common/src/main/c/netty_unix_limits.h new file mode 100644 index 0000000000..dcb18c83f9 --- /dev/null +++ b/transport-native-unix-common/src/main/c/netty_unix_limits.h @@ -0,0 +1,25 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +#ifndef NETTY_UNIX_LIMITS_H_ +#define NETTY_UNIX_LIMITS_H_ + +#include + +// JNI initialization hooks. Users of this file are responsible for calling these in the JNI_OnLoad and JNI_OnUnload methods. +jint netty_unix_limits_JNI_OnLoad(JNIEnv* env, const char* packagePrefix); +void netty_unix_limits_JNI_OnUnLoad(JNIEnv* env); + +#endif /* NETTY_UNIX_LIMITS_H_ */ diff --git a/transport-native-epoll/src/main/c/netty_unix_socket.c b/transport-native-unix-common/src/main/c/netty_unix_socket.c similarity index 77% rename from transport-native-epoll/src/main/c/netty_unix_socket.c rename to transport-native-unix-common/src/main/c/netty_unix_socket.c index 77522d0b3a..d0da2769c9 100644 --- a/transport-native-epoll/src/main/c/netty_unix_socket.c +++ b/transport-native-unix-common/src/main/c/netty_unix_socket.c @@ -13,12 +13,6 @@ * License for the specific language governing permissions and limitations * under the License. */ - /* - * Since glibc 2.8, the _GNU_SOURCE feature test macro must be defined - * (before including any header files) in order to obtain the - * definition of this structure. See - */ -#define _GNU_SOURCE #include #include #include @@ -28,17 +22,22 @@ #include #include #include +#include #include #include "netty_unix_errors.h" #include "netty_unix_socket.h" #include "netty_unix_util.h" +// Define SO_REUSEPORT if not found to fix build issues. +// See https://github.com/netty/netty/issues/2558 +#ifndef SO_REUSEPORT +#define SO_REUSEPORT 15 +#endif /* SO_REUSEPORT */ + static jclass datagramSocketAddressClass = NULL; -static jclass peerCredentialsClass = NULL; static jmethodID datagramSocketAddrMethodId = NULL; static jmethodID inetSocketAddrMethodId = NULL; -static jmethodID peerCredentialsMethodId = NULL; static jclass inetSocketAddressClass = NULL; static jclass netUtilClass = NULL; static jmethodID netUtilClassIpv4PreferredMethodId = NULL; @@ -48,12 +47,28 @@ static const unsigned char wildcardAddress[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0 static const unsigned char ipv4MappedWildcardAddress[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 }; // Optional external methods -extern int accept4(int sockFd, struct sockaddr* addr, socklen_t* addrlen, int flags) __attribute__((weak)); +extern int accept4(int sockFd, struct sockaddr* addr, socklen_t* addrlen, int flags) __attribute__((weak)) __attribute__((weak_import)); // macro to calculate the length of a sockaddr_un struct for a given path length. // see sys/un.h#SUN_LEN, this is modified to allow nul bytes #define _UNIX_ADDR_LENGTH(path_len) (uintptr_t) (((struct sockaddr_un *) 0)->sun_path) + path_len +static int nettyNonBlockingSocket(int domain, int type, int protocol) { +#ifdef SOCK_NONBLOCK + return socket(domain, type | SOCK_NONBLOCK, protocol); +#else + int socketFd = socket(domain, type, protocol); + int flags; + // Don't initialize flags until we know the socket is good so errno is preserved. + if (socketFd < 0 || + (flags = fcntl(socketFd, F_GETFL, 0)) < 0 || + fcntl(socketFd, F_SETFL, flags | O_NONBLOCK) < 0) { + return -1; + } + return socketFd; +#endif +} + static jobject createDatagramSocketAddress(JNIEnv* env, const struct sockaddr_storage* addr, int len) { char ipstr[INET6_ADDRSTRLEN]; int port; @@ -83,23 +98,21 @@ static jobject createDatagramSocketAddress(JNIEnv* env, const struct sockaddr_st return socketAddr; } -static int addressLength(const struct sockaddr_storage* addr) { +static jsize addressLength(const struct sockaddr_storage* addr) { if (addr->ss_family == AF_INET) { return 8; - } else { - struct sockaddr_in6* s = (struct sockaddr_in6*) addr; - if (s->sin6_addr.s6_addr[0] == 0x00 && s->sin6_addr.s6_addr[1] == 0x00 && s->sin6_addr.s6_addr[2] == 0x00 && s->sin6_addr.s6_addr[3] == 0x00 && s->sin6_addr.s6_addr[4] == 0x00 - && s->sin6_addr.s6_addr[5] == 0x00 && s->sin6_addr.s6_addr[6] == 0x00 && s->sin6_addr.s6_addr[7] == 0x00 && s->sin6_addr.s6_addr[8] == 0x00 && s->sin6_addr.s6_addr[9] == 0x00 - && s->sin6_addr.s6_addr[10] == 0xff && s->sin6_addr.s6_addr[11] == 0xff) { - // IPv4-mapped-on-IPv6 - return 8; - } else { - return 24; - } } + struct sockaddr_in6* s = (struct sockaddr_in6*) addr; + if (s->sin6_addr.s6_addr[11] == 0xff && s->sin6_addr.s6_addr[10] == 0xff && + s->sin6_addr.s6_addr[9] == 0x00 && s->sin6_addr.s6_addr[8] == 0x00 && s->sin6_addr.s6_addr[7] == 0x00 && s->sin6_addr.s6_addr[6] == 0x00 && s->sin6_addr.s6_addr[5] == 0x00 && + s->sin6_addr.s6_addr[4] == 0x00 && s->sin6_addr.s6_addr[3] == 0x00 && s->sin6_addr.s6_addr[2] == 0x00 && s->sin6_addr.s6_addr[1] == 0x00 && s->sin6_addr.s6_addr[0] == 0x00) { + // IPv4-mapped-on-IPv6 + return 8; + } + return 24; } -static void initInetSocketAddressArray(JNIEnv* env, const struct sockaddr_storage* addr, jbyteArray bArray, int offset, int len) { +static void initInetSocketAddressArray(JNIEnv* env, const struct sockaddr_storage* addr, jbyteArray bArray, int offset, jsize len) { int port; if (addr->ss_family == AF_INET) { struct sockaddr_in* s = (struct sockaddr_in*) addr; @@ -148,7 +161,7 @@ static void initInetSocketAddressArray(JNIEnv* env, const struct sockaddr_storag } static jbyteArray createInetSocketAddressArray(JNIEnv* env, const struct sockaddr_storage* addr) { - int len = addressLength(addr); + jsize len = addressLength(addr); jbyteArray bArray = (*env)->NewByteArray(env, len); initInetSocketAddressArray(env, addr, bArray, 0, len); @@ -162,7 +175,7 @@ static int socket_type(JNIEnv* env) { // User asked to use ipv4 explicitly. return AF_INET; } - int fd = socket(AF_INET6, SOCK_STREAM | SOCK_NONBLOCK, 0); + int fd = nettyNonBlockingSocket(AF_INET6, SOCK_STREAM, 0); if (fd == -1) { if (errno == EAFNOSUPPORT) { return AF_INET; @@ -175,7 +188,7 @@ static int socket_type(JNIEnv* env) { } static jint _socket(JNIEnv* env, jclass clazz, int type) { - int fd = socket(socketType, type | SOCK_NONBLOCK, 0); + int fd = nettyNonBlockingSocket(socketType, type, 0); if (fd == -1) { return -errno; } else if (socketType == AF_INET6) { @@ -190,7 +203,8 @@ static jint _socket(JNIEnv* env, jclass clazz, int type) { return fd; } -int netty_unix_socket_initSockaddr(JNIEnv* env, jbyteArray address, jint scopeId, jint jport, const struct sockaddr_storage* addr) { +int netty_unix_socket_initSockaddr(JNIEnv* env, jbyteArray address, jint scopeId, jint jport, + const struct sockaddr_storage* addr, socklen_t* addrSize) { uint16_t port = htons((uint16_t) jport); // Use GetPrimitiveArrayCritical and ReleasePrimitiveArrayCritical to signal the VM that we really would like @@ -204,12 +218,11 @@ int netty_unix_socket_initSockaddr(JNIEnv* env, jbyteArray address, jint scopeId } if (socketType == AF_INET6) { struct sockaddr_in6* ip6addr = (struct sockaddr_in6*) addr; + *addrSize = sizeof(struct sockaddr_in6); ip6addr->sin6_family = AF_INET6; ip6addr->sin6_port = port; - - if (scopeId != 0) { - ip6addr->sin6_scope_id = (uint32_t) scopeId; - } + ip6addr->sin6_flowinfo = 0; + ip6addr->sin6_scope_id = (uint32_t) scopeId; // check if this is an any address and if so we need to handle it like this. if (memcmp(addressBytes, wildcardAddress, 16) == 0 || memcmp(addressBytes, ipv4MappedWildcardAddress, 16) == 0) { ip6addr->sin6_addr = in6addr_any; @@ -218,6 +231,7 @@ int netty_unix_socket_initSockaddr(JNIEnv* env, jbyteArray address, jint scopeId } } else { struct sockaddr_in* ipaddr = (struct sockaddr_in*) addr; + *addrSize = sizeof(struct sockaddr_in); ipaddr->sin_family = AF_INET; ipaddr->sin_port = port; memcpy(&(ipaddr->sin_addr.s_addr), addressBytes + 12, 4); @@ -229,14 +243,15 @@ int netty_unix_socket_initSockaddr(JNIEnv* env, jbyteArray address, jint scopeId static jint _sendTo(JNIEnv* env, jint fd, void* buffer, jint pos, jint limit, jbyteArray address, jint scopeId, jint port) { struct sockaddr_storage addr; - if (netty_unix_socket_initSockaddr(env, address, scopeId, port, &addr) == -1) { + socklen_t addrSize; + if (netty_unix_socket_initSockaddr(env, address, scopeId, port, &addr, &addrSize) == -1) { return -1; } ssize_t res; int err; do { - res = sendto(fd, buffer + pos, (size_t) (limit - pos), 0, (struct sockaddr*) &addr, sizeof(struct sockaddr_storage)); + res = sendto(fd, buffer + pos, (size_t) (limit - pos), 0, (struct sockaddr*) &addr, addrSize); // keep on writing if it was interrupted } while (res == -1 && ((err = errno) == EINTR)); @@ -319,11 +334,12 @@ static jint netty_unix_socket_shutdown(JNIEnv* env, jclass clazz, jint fd, jbool static jint netty_unix_socket_bind(JNIEnv* env, jclass clazz, jint fd, jbyteArray address, jint scopeId, jint port) { struct sockaddr_storage addr; - if (netty_unix_socket_initSockaddr(env, address, scopeId, port, &addr) == -1) { + socklen_t addrSize; + if (netty_unix_socket_initSockaddr(env, address, scopeId, port, &addr, &addrSize) == -1) { return -1; } - if (bind(fd, (struct sockaddr*) &addr, sizeof(addr)) == -1) { + if (bind(fd, (struct sockaddr*) &addr, addrSize) == -1) { return -errno; } return 0; @@ -338,7 +354,8 @@ static jint netty_unix_socket_listen(JNIEnv* env, jclass clazz, jint fd, jint ba static jint netty_unix_socket_connect(JNIEnv* env, jclass clazz, jint fd, jbyteArray address, jint scopeId, jint port) { struct sockaddr_storage addr; - if (netty_unix_socket_initSockaddr(env, address, scopeId, port, &addr) == -1) { + socklen_t addrSize; + if (netty_unix_socket_initSockaddr(env, address, scopeId, port, &addr, &addrSize) == -1) { // A runtime exception was thrown return -1; } @@ -346,7 +363,7 @@ static jint netty_unix_socket_connect(JNIEnv* env, jclass clazz, jint fd, jbyteA int res; int err; do { - res = connect(fd, (struct sockaddr*) &addr, sizeof(addr)); + res = connect(fd, (struct sockaddr*) &addr, addrSize); } while (res == -1 && ((err = errno) == EINTR)); if (res < 0) { @@ -375,23 +392,31 @@ static jint netty_unix_socket_finishConnect(JNIEnv* env, jclass clazz, jint fd) static jint netty_unix_socket_accept(JNIEnv* env, jclass clazz, jint fd, jbyteArray acceptedAddress) { jint socketFd; + jsize len; int err; struct sockaddr_storage addr; socklen_t address_len = sizeof(addr); - do { + for (;;) { +#ifdef SOCK_NONBLOCK if (accept4) { socketFd = accept4(fd, (struct sockaddr*) &addr, &address_len, SOCK_NONBLOCK | SOCK_CLOEXEC); - } else { + } else { +#endif socketFd = accept(fd, (struct sockaddr*) &addr, &address_len); +#ifdef SOCK_NONBLOCK } - } while (socketFd == -1 && ((err = errno) == EINTR)); +#endif - if (socketFd == -1) { - return -err; + if (socketFd != -1) { + break; + } + if ((err = errno) != EINTR) { + return -err; + } } - int len = addressLength(&addr); + len = addressLength(&addr); // Fill in remote address details (*env)->SetByteArrayRegion(env, acceptedAddress, 0, 4, (jbyte*) &len); @@ -399,11 +424,10 @@ static jint netty_unix_socket_accept(JNIEnv* env, jclass clazz, jint fd, jbyteAr if (accept4) { return socketFd; - } else { + } + if (fcntl(socketFd, F_SETFD, FD_CLOEXEC) == -1 || fcntl(socketFd, F_SETFL, O_NONBLOCK) == -1) { // accept4 was not present so need two more sys-calls ... - if (fcntl(socketFd, F_SETFD, FD_CLOEXEC) == -1 || fcntl(socketFd, F_SETFL, O_NONBLOCK) == -1) { - return -errno; - } + return -errno; } return socketFd; } @@ -435,7 +459,7 @@ static jint netty_unix_socket_newSocketStreamFd(JNIEnv* env, jclass clazz) { } static jint netty_unix_socket_newSocketDomainFd(JNIEnv* env, jclass clazz) { - int fd = socket(PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0); + int fd = nettyNonBlockingSocket(PF_UNIX, SOCK_STREAM, 0); if (fd == -1) { return -errno; } @@ -453,14 +477,14 @@ static jint netty_unix_socket_sendToAddress(JNIEnv* env, jclass clazz, jint fd, static jint netty_unix_socket_sendToAddresses(JNIEnv* env, jclass clazz, jint fd, jlong memoryAddress, jint length, jbyteArray address, jint scopeId, jint port) { struct sockaddr_storage addr; - - if (netty_unix_socket_initSockaddr(env, address, scopeId, port, &addr) == -1) { + socklen_t addrSize; + if (netty_unix_socket_initSockaddr(env, address, scopeId, port, &addr, &addrSize) == -1) { return -1; } struct msghdr m; m.msg_name = (void*) &addr; - m.msg_namelen = (socklen_t) sizeof(struct sockaddr_storage); + m.msg_namelen = addrSize; m.msg_iov = (struct iovec*) (intptr_t) memoryAddress; m.msg_iovlen = length; @@ -499,7 +523,7 @@ static jint netty_unix_socket_bindDomainSocket(JNIEnv* env, jclass clazz, jint f } memcpy(addr.sun_path, socket_path, socket_path_len); - if (unlink(socket_path) == -1 && errno != ENOENT) { + if (unlink((const char*) socket_path) == -1 && errno != ENOENT) { return -errno; } @@ -540,6 +564,98 @@ static jint netty_unix_socket_connectDomainSocket(JNIEnv* env, jclass clazz, jin return 0; } +static jint netty_unix_socket_recvFd(JNIEnv* env, jclass clazz, jint fd) { + int socketFd; + struct msghdr descriptorMessage = { 0 }; + struct iovec iov[1] = { 0 }; + char control[CMSG_SPACE(sizeof(int))] = { 0 }; + char iovecData[1]; + + descriptorMessage.msg_control = control; + descriptorMessage.msg_controllen = sizeof(control); + descriptorMessage.msg_iov = iov; + descriptorMessage.msg_iovlen = 1; + iov[0].iov_base = iovecData; + iov[0].iov_len = sizeof(iovecData); + + ssize_t res; + int err; + + for (;;) { + do { + res = recvmsg(fd, &descriptorMessage, 0); + // Keep on reading if we was interrupted + } while (res == -1 && ((err = errno) == EINTR)); + + if (res == 0) { + return 0; + } + + if (res < 0) { + return -err; + } + + struct cmsghdr* cmsg = CMSG_FIRSTHDR(&descriptorMessage); + if (!cmsg) { + return -errno; + } + + if ((cmsg->cmsg_len == CMSG_LEN(sizeof(int))) && (cmsg->cmsg_level == SOL_SOCKET) && (cmsg->cmsg_type == SCM_RIGHTS)) { + socketFd = *((int *) CMSG_DATA(cmsg)); + // set as non blocking as we want to use it with kqueue/epoll + if (fcntl(socketFd, F_SETFL, O_NONBLOCK) == -1) { + err = errno; + close(socketFd); + return -err; + } + return socketFd; + } + } +} + +static jint netty_unix_socket_sendFd(JNIEnv* env, jclass clazz, jint socketFd, jint fd) { + struct msghdr descriptorMessage = { 0 }; + struct iovec iov[1] = { 0 }; + char control[CMSG_SPACE(sizeof(int))] = { 0 }; + char iovecData[1]; + + descriptorMessage.msg_control = control; + descriptorMessage.msg_controllen = sizeof(control); + struct cmsghdr* cmsg = CMSG_FIRSTHDR(&descriptorMessage); + + if (cmsg) { + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + *((int *)CMSG_DATA(cmsg)) = fd; + descriptorMessage.msg_iov = iov; + descriptorMessage.msg_iovlen = 1; + iov[0].iov_base = iovecData; + iov[0].iov_len = sizeof(iovecData); + + ssize_t res; + int err; + do { + res = sendmsg(socketFd, &descriptorMessage, 0); + // keep on writing if it was interrupted + } while (res == -1 && ((err = errno) == EINTR)); + + if (res < 0) { + return -err; + } + return (jint) res; + } + return -1; +} + +static void netty_unix_socket_setReuseAddress(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); +} + +static void netty_unix_socket_setReusePort(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)); +} + static void netty_unix_socket_setTcpNoDelay(JNIEnv* env, jclass clazz, jint fd, jint optval) { netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval)); } @@ -556,8 +672,8 @@ static void netty_unix_socket_setKeepAlive(JNIEnv* env, jclass clazz, jint fd, j netty_unix_socket_setOption(env, fd, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)); } -static void netty_unix_socket_setTcpCork(JNIEnv* env, jclass clazz, jint fd, jint optval) { - netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_CORK, &optval, sizeof(optval)); +static void netty_unix_socket_setBroadcast(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval)); } static void netty_unix_socket_setSoLinger(JNIEnv* env, jclass clazz, jint fd, jint optval) { @@ -572,12 +688,14 @@ static void netty_unix_socket_setSoLinger(JNIEnv* env, jclass clazz, jint fd, ji netty_unix_socket_setOption(env, fd, SOL_SOCKET, SO_LINGER, &solinger, sizeof(solinger)); } -static void netty_unix_socket_setTcpDeferAccept(JNIEnv* env, jclass clazz, jint fd, jint optval) { - netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &optval, sizeof(optval)); -} +static void netty_unix_socket_setTrafficClass(JNIEnv* env, jclass clazz, jint fd, jint optval) { + netty_unix_socket_setOption(env, fd, IPPROTO_IP, IP_TOS, &optval, sizeof(optval)); -static void netty_unix_socket_setTcpQuickAck(JNIEnv* env, jclass clazz, jint fd, jint optval) { - netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_QUICKACK, &optval, sizeof(optval)); + /* Try to set the ipv6 equivalent, but don't throw if this is an ipv4 only socket. */ + int rc = netty_unix_socket_setOption(env, fd, IPPROTO_IPV6, IPV6_TCLASS, &optval, sizeof(optval)); + if (rc < 0 && errno != ENOPROTOOPT) { + netty_unix_errors_throwChannelExceptionErrorNo(env, "setting ipv6 dscp failed: ", errno); + } } static jint netty_unix_socket_isKeepAlive(JNIEnv* env, jclass clazz, jint fd) { @@ -612,14 +730,6 @@ static jint netty_unix_socket_getSendBufferSize(JNIEnv* env, jclass clazz, jint return optval; } -static jint netty_unix_socket_isTcpCork(JNIEnv* env, jclass clazz, jint fd) { - int optval; - if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_CORK, &optval, sizeof(optval)) == -1) { - return -1; - } - return optval; -} - static jint netty_unix_socket_getSoLinger(JNIEnv* env, jclass clazz, jint fd) { struct linger optval; if (netty_unix_socket_getOption(env, fd, SOL_SOCKET, SO_LINGER, &optval, sizeof(optval)) == -1) { @@ -632,6 +742,14 @@ static jint netty_unix_socket_getSoLinger(JNIEnv* env, jclass clazz, jint fd) { } } +static jint netty_unix_socket_getTrafficClass(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, IPPROTO_IP, IP_TOS, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; +} + static jint netty_unix_socket_getSoError(JNIEnv* env, jclass clazz, jint fd) { int optval; if (netty_unix_socket_getOption(env, fd, SOL_SOCKET, SO_ERROR, &optval, sizeof(optval)) == -1) { @@ -640,28 +758,28 @@ static jint netty_unix_socket_getSoError(JNIEnv* env, jclass clazz, jint fd) { return optval; } -static jint netty_unix_socket_getTcpDeferAccept(JNIEnv* env, jclass clazz, jint fd) { +static jint netty_unix_socket_isReuseAddress(JNIEnv* env, jclass clazz, jint fd) { int optval; - if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &optval, sizeof(optval)) == -1) { + if (netty_unix_socket_getOption(env, fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) { return -1; } return optval; } -static jint netty_unix_socket_isTcpQuickAck(JNIEnv* env, jclass clazz, jint fd) { +static jint netty_unix_socket_isReusePort(JNIEnv* env, jclass clazz, jint fd) { int optval; - if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_QUICKACK, &optval, sizeof(optval)) == -1) { + if (netty_unix_socket_getOption(env, fd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)) == -1) { return -1; } return optval; } -static jobject netty_channel_unix_socket_getPeerCredentials(JNIEnv *env, jclass clazz, jint fd) { - struct ucred credentials; - if(netty_unix_socket_getOption(env,fd, SOL_SOCKET, SO_PEERCRED, &credentials, sizeof (credentials))) { - return NULL; - } - return (*env)->NewObject(env, peerCredentialsClass, peerCredentialsMethodId, credentials.pid, credentials.uid, credentials.gid); +static jint netty_unix_socket_isBroadcast(JNIEnv* env, jclass clazz, jint fd) { + int optval; + if (netty_unix_socket_getOption(env, fd, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval)) == -1) { + return -1; + } + return optval; } // JNI Registered Methods End @@ -683,30 +801,34 @@ static const JNINativeMethod fixed_method_table[] = { { "sendToAddresses", "(IJI[BII)I", (void *) netty_unix_socket_sendToAddresses }, // "recvFrom" has a dynamic signature // "recvFromAddress" has a dynamic signature + { "recvFd", "(I)I", (void *) netty_unix_socket_recvFd }, + { "sendFd", "(II)I", (void *) netty_unix_socket_sendFd }, { "bindDomainSocket", "(I[B)I", (void *) netty_unix_socket_bindDomainSocket }, { "connectDomainSocket", "(I[B)I", (void *) netty_unix_socket_connectDomainSocket }, { "setTcpNoDelay", "(II)V", (void *) netty_unix_socket_setTcpNoDelay }, + { "setReusePort", "(II)V", (void *) netty_unix_socket_setReusePort }, + { "setBroadcast", "(II)V", (void *) netty_unix_socket_setBroadcast }, + { "setReuseAddress", "(II)V", (void *) netty_unix_socket_setReuseAddress }, { "setReceiveBufferSize", "(II)V", (void *) netty_unix_socket_setReceiveBufferSize }, { "setSendBufferSize", "(II)V", (void *) netty_unix_socket_setSendBufferSize }, { "setKeepAlive", "(II)V", (void *) netty_unix_socket_setKeepAlive }, - { "setTcpCork", "(II)V", (void *) netty_unix_socket_setTcpCork }, { "setSoLinger", "(II)V", (void *) netty_unix_socket_setSoLinger }, - { "setTcpDeferAccept", "(II)V", (void *) netty_unix_socket_setTcpDeferAccept }, - { "setTcpQuickAck", "(II)V", (void *) netty_unix_socket_setTcpQuickAck }, + { "setTrafficClass", "(II)V", (void *) netty_unix_socket_setTrafficClass }, { "isKeepAlive", "(I)I", (void *) netty_unix_socket_isKeepAlive }, { "isTcpNoDelay", "(I)I", (void *) netty_unix_socket_isTcpNoDelay }, + { "isBroadcast", "(I)I", (void *) netty_unix_socket_isBroadcast }, + { "isReuseAddress", "(I)I", (void *) netty_unix_socket_isReuseAddress }, + { "isReusePort", "(I)I", (void *) netty_unix_socket_isReusePort }, { "getReceiveBufferSize", "(I)I", (void *) netty_unix_socket_getReceiveBufferSize }, { "getSendBufferSize", "(I)I", (void *) netty_unix_socket_getSendBufferSize }, - { "isTcpCork", "(I)I", (void *) netty_unix_socket_isTcpCork }, { "getSoLinger", "(I)I", (void *) netty_unix_socket_getSoLinger }, - { "getSoError", "(I)I", (void *) netty_unix_socket_getSoError }, - { "getTcpDeferAccept", "(I)I", (void *) netty_unix_socket_getTcpDeferAccept }, - { "isTcpQuickAck", "(I)I", (void *) netty_unix_socket_isTcpQuickAck } + { "getTrafficClass", "(I)I", (void *) netty_unix_socket_getTrafficClass }, + { "getSoError", "(I)I", (void *) netty_unix_socket_getSoError } }; static const jint fixed_method_table_size = sizeof(fixed_method_table) / sizeof(fixed_method_table[0]); static jint dynamicMethodsTableSize() { - return fixed_method_table_size + 3; + return fixed_method_table_size + 2; } static JNINativeMethod* createDynamicMethodsTable(const char* packagePrefix) { @@ -718,18 +840,14 @@ static JNINativeMethod* createDynamicMethodsTable(const char* packagePrefix) { dynamicMethod->signature = netty_unix_util_prepend("(ILjava/nio/ByteBuffer;II)L", dynamicTypeName); dynamicMethod->fnPtr = (void *) netty_unix_socket_recvFrom; free(dynamicTypeName); + ++dynamicMethod; dynamicTypeName = netty_unix_util_prepend(packagePrefix, "io/netty/channel/unix/DatagramSocketAddress;"); dynamicMethod->name = "recvFromAddress"; dynamicMethod->signature = netty_unix_util_prepend("(IJII)L", dynamicTypeName); dynamicMethod->fnPtr = (void *) netty_unix_socket_recvFromAddress; free(dynamicTypeName); - ++dynamicMethod; - dynamicTypeName = netty_unix_util_prepend(packagePrefix, "io/netty/channel/unix/PeerCredentials;"); - dynamicMethod->name = "getPeerCredentials"; - dynamicMethod->signature = netty_unix_util_prepend("(I)L", dynamicTypeName); - dynamicMethod->fnPtr = (void *) netty_channel_unix_socket_getPeerCredentials; - free(dynamicTypeName); + return dynamicMethods; } @@ -810,26 +928,6 @@ jint netty_unix_socket_JNI_OnLoad(JNIEnv* env, const char* packagePrefix) { netty_unix_errors_throwRuntimeException(env, "failed to get method ID: NetUild.isIpV4StackPreferred()"); return JNI_ERR; } - - nettyClassName = netty_unix_util_prepend(packagePrefix, "io/netty/channel/unix/PeerCredentials"); - jclass localPeerCredsClass = (*env)->FindClass(env, nettyClassName); - free(nettyClassName); - nettyClassName = NULL; - if (localPeerCredsClass == NULL) { - // pending exception... - return JNI_ERR; - } - peerCredentialsClass = (jclass) (*env)->NewGlobalRef(env, localPeerCredsClass); - if (peerCredentialsClass == NULL) { - // out-of-memory! - netty_unix_errors_throwOutOfMemoryError(env); - return JNI_ERR; - } - peerCredentialsMethodId = (*env)->GetMethodID(env, peerCredentialsClass, "", "(III)V"); - if (peerCredentialsMethodId == NULL) { - netty_unix_errors_throwRuntimeException(env, "failed to get method ID: PeerCredentials.(int, int, int)"); - return JNI_ERR; - } void* mem = malloc(1); if (mem == NULL) { @@ -868,8 +966,4 @@ void netty_unix_socket_JNI_OnUnLoad(JNIEnv* env) { (*env)->DeleteGlobalRef(env, netUtilClass); netUtilClass = NULL; } - if (peerCredentialsClass != NULL) { - (*env)->DeleteGlobalRef(env, peerCredentialsClass); - peerCredentialsClass = NULL; - } } diff --git a/transport-native-epoll/src/main/c/netty_unix_socket.h b/transport-native-unix-common/src/main/c/netty_unix_socket.h similarity index 98% rename from transport-native-epoll/src/main/c/netty_unix_socket.h rename to transport-native-unix-common/src/main/c/netty_unix_socket.h index c149632a4a..99d27c0983 100644 --- a/transport-native-epoll/src/main/c/netty_unix_socket.h +++ b/transport-native-unix-common/src/main/c/netty_unix_socket.h @@ -20,7 +20,7 @@ #include // External C methods -int netty_unix_socket_initSockaddr(JNIEnv* env, jbyteArray address, jint scopeId, jint jport, const struct sockaddr_storage* addr); +int netty_unix_socket_initSockaddr(JNIEnv* env, jbyteArray address, jint scopeId, jint jport, const struct sockaddr_storage* addr, socklen_t* addrSize); int netty_unix_socket_getOption(JNIEnv* env, jint fd, int level, int optname, void* optval, socklen_t optlen); int netty_unix_socket_setOption(JNIEnv* env, jint fd, int level, int optname, const void* optval, socklen_t len); diff --git a/transport-native-unix-common/src/main/c/netty_unix_util.c b/transport-native-unix-common/src/main/c/netty_unix_util.c new file mode 100644 index 0000000000..903ae607cf --- /dev/null +++ b/transport-native-unix-common/src/main/c/netty_unix_util.c @@ -0,0 +1,165 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include "netty_unix_util.h" + +#ifdef NETTY_USE_MACH_INSTEAD_OF_CLOCK + +#include +#include +static const uint64_t NETTY_BILLION = 1000000000L; + +#endif /* NETTY_USE_MACH_INSTEAD_OF_CLOCK */ + +char* netty_unix_util_prepend(const char* prefix, const char* str) { + if (prefix == NULL) { + char* result = (char*) malloc(sizeof(char) * (strlen(str) + 1)); + strcpy(result, str); + return result; + } + char* result = (char*) malloc(sizeof(char) * (strlen(prefix) + strlen(str) + 1)); + strcpy(result, prefix); + strcat(result, str); + return result; +} + +char* netty_unix_util_rstrstr(char* s1rbegin, const char* s1rend, const char* s2) { + size_t s2len = strlen(s2); + char *s = s1rbegin - s2len; + + for (; s >= s1rend; --s) { + if (strncmp(s, s2, s2len) == 0) { + return s; + } + } + return NULL; +} + +char* netty_unix_util_parse_package_prefix(const char* libraryPathName, const char* libraryName, jint* status) { + char* packageNameEnd = strstr(libraryPathName, libraryName); + if (packageNameEnd == NULL) { + *status = JNI_ERR; + return NULL; + } + char* packagePrefix = netty_unix_util_rstrstr(packageNameEnd, libraryPathName, "lib"); + if (packagePrefix == NULL) { + *status = JNI_ERR; + return NULL; + } + packagePrefix += 3; + if (packagePrefix == packageNameEnd) { + return NULL; + } + // packagePrefix length is > 0 + // Make a copy so we can modify the value without impacting libraryPathName. + size_t packagePrefixLen = packageNameEnd - packagePrefix; + packagePrefix = strndup(packagePrefix, packagePrefixLen); + // Make sure the packagePrefix is in the correct format for the JNI functions it will be used with. + char* temp = packagePrefix; + packageNameEnd = packagePrefix + packagePrefixLen; + // Package names must be sanitized, in JNI packages names are separated by '/' characters. + for (; temp != packageNameEnd; ++temp) { + if (*temp == '-') { + *temp = '/'; + } + } + // Make sure packagePrefix is terminated with the '/' JNI package separator. + if(*(--temp) != '/') { + temp = packagePrefix; + packagePrefix = netty_unix_util_prepend(packagePrefix, "/"); + free(temp); + } + return packagePrefix; +} + +// util methods +int netty_unix_util_clock_gettime(clockid_t clockId, struct timespec* tp) { +#ifdef NETTY_USE_MACH_INSTEAD_OF_CLOCK + uint64_t timeNs; + switch (clockId) { + case CLOCK_MONOTONIC_COARSE: + timeNs = mach_approximate_time(); + break; + case CLOCK_MONOTONIC: + timeNs = mach_absolute_time(); + break; + default: + errno = EINVAL; + return -1; + } + // NOTE: this could overflow if time_t is backed by a 32 bit number. + tp->tv_sec = timeNs / NETTY_BILLION; + tp->tv_nsec = timeNs - tp->tv_sec * NETTY_BILLION; // avoid using modulo if not necessary + return 0; +#else + return clock_gettime(clockId, tp); +#endif /* NETTY_USE_MACH_INSTEAD_OF_CLOCK */ +} + +jboolean netty_unix_util_initialize_wait_clock(clockid_t* clockId) { + struct timespec ts; + // First try to get a monotonic clock, as we effectively measure execution time and don't want the underlying clock + // moving unexpectedly/abruptly. +#ifdef CLOCK_MONOTONIC_COARSE + *clockId = CLOCK_MONOTONIC_COARSE; + if (netty_unix_util_clock_gettime(*clockId, &ts) == 0) { + return JNI_TRUE; + } +#endif +#ifdef CLOCK_MONOTONIC_RAW + *clockId = CLOCK_MONOTONIC_RAW; + if (netty_unix_util_clock_gettime(*clockId, &ts) == 0) { + return JNI_TRUE; + } +#endif +#ifdef CLOCK_MONOTONIC + *clockId = CLOCK_MONOTONIC; + if (netty_unix_util_clock_gettime(*clockId, &ts) == 0) { + return JNI_TRUE; + } +#endif + + // Fallback to realtime ... in this case we are subject to clock movements on the system. +#ifdef CLOCK_REALTIME_COARSE + *clockId = CLOCK_REALTIME_COARSE; + if (netty_unix_util_clock_gettime(*clockId, &ts) == 0) { + return JNI_TRUE; + } +#endif +#ifdef CLOCK_REALTIME + *clockId = CLOCK_REALTIME; + if (netty_unix_util_clock_gettime(*clockId, &ts) == 0) { + return JNI_TRUE; + } +#endif + + return JNI_FALSE; +} + +jint netty_unix_util_register_natives(JNIEnv* env, const char* packagePrefix, const char* className, const JNINativeMethod* methods, jint numMethods) { + char* nettyClassName = netty_unix_util_prepend(packagePrefix, className); + jclass nativeCls = (*env)->FindClass(env, nettyClassName); + free(nettyClassName); + nettyClassName = NULL; + if (nativeCls == NULL) { + return JNI_ERR; + } + + return (*env)->RegisterNatives(env, nativeCls, methods, numMethods); +} diff --git a/transport-native-epoll/src/main/c/netty_unix_util.h b/transport-native-unix-common/src/main/c/netty_unix_util.h similarity index 57% rename from transport-native-epoll/src/main/c/netty_unix_util.h rename to transport-native-unix-common/src/main/c/netty_unix_util.h index f42b961225..9f75e32005 100644 --- a/transport-native-epoll/src/main/c/netty_unix_util.h +++ b/transport-native-unix-common/src/main/c/netty_unix_util.h @@ -18,6 +18,22 @@ #define NETTY_UNIX_UTIL_H_ #include +#include + +#if defined(__MACH__) && !defined(CLOCK_REALTIME) +#define NETTY_USE_MACH_INSTEAD_OF_CLOCK + +typedef int clockid_t; + +#ifndef CLOCK_MONOTONIC +#define CLOCK_MONOTONIC 1 +#endif + +#ifndef CLOCK_MONOTONIC_COARSE +#define CLOCK_MONOTONIC_COARSE 2 +#endif + +#endif /* __MACH__ */ /** * Return a new string (caller must free this string) which is equivalent to
prefix + str
. @@ -28,6 +44,26 @@ char* netty_unix_util_prepend(const char* prefix, const char* str); char* netty_unix_util_rstrstr(char* s1rbegin, const char* s1rend, const char* s2); +/** + * The expected format of the library name is "lib<>$libraryName" where the <> portion is what we will return. + * If status != JNI_ERR then the caller MUST call free on the return value. + */ +char* netty_unix_util_parse_package_prefix(const char* libraryPathName, const char* libraryName, jint* status); + +/** + * Get a clock which can be used to measure execution time. + * + * Returns true is a suitable clock was found. + */ +jboolean netty_unix_util_initialize_wait_clock(clockid_t* clockId); + +/** + * This will delegate to clock_gettime from time.h if the platform supports it. + * + * MacOS does not support clock_gettime. + */ +int netty_unix_util_clock_gettime(clockid_t clockId, struct timespec* tp); + /** * Return type is as defined in http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html#wp5833. */ diff --git a/transport-native-epoll/src/main/java/io/netty/channel/unix/DatagramSocketAddress.java b/transport-native-unix-common/src/main/java/io/netty/channel/unix/DatagramSocketAddress.java similarity index 100% rename from transport-native-epoll/src/main/java/io/netty/channel/unix/DatagramSocketAddress.java rename to transport-native-unix-common/src/main/java/io/netty/channel/unix/DatagramSocketAddress.java diff --git a/transport-native-epoll/src/main/java/io/netty/channel/unix/DomainSocketAddress.java b/transport-native-unix-common/src/main/java/io/netty/channel/unix/DomainSocketAddress.java similarity index 100% rename from transport-native-epoll/src/main/java/io/netty/channel/unix/DomainSocketAddress.java rename to transport-native-unix-common/src/main/java/io/netty/channel/unix/DomainSocketAddress.java diff --git a/transport-native-epoll/src/main/java/io/netty/channel/unix/DomainSocketChannel.java b/transport-native-unix-common/src/main/java/io/netty/channel/unix/DomainSocketChannel.java similarity index 100% rename from transport-native-epoll/src/main/java/io/netty/channel/unix/DomainSocketChannel.java rename to transport-native-unix-common/src/main/java/io/netty/channel/unix/DomainSocketChannel.java diff --git a/transport-native-epoll/src/main/java/io/netty/channel/unix/DomainSocketChannelConfig.java b/transport-native-unix-common/src/main/java/io/netty/channel/unix/DomainSocketChannelConfig.java similarity index 100% rename from transport-native-epoll/src/main/java/io/netty/channel/unix/DomainSocketChannelConfig.java rename to transport-native-unix-common/src/main/java/io/netty/channel/unix/DomainSocketChannelConfig.java diff --git a/transport-native-epoll/src/main/java/io/netty/channel/unix/DomainSocketReadMode.java b/transport-native-unix-common/src/main/java/io/netty/channel/unix/DomainSocketReadMode.java similarity index 100% rename from transport-native-epoll/src/main/java/io/netty/channel/unix/DomainSocketReadMode.java rename to transport-native-unix-common/src/main/java/io/netty/channel/unix/DomainSocketReadMode.java diff --git a/transport-native-epoll/src/main/java/io/netty/channel/unix/Errors.java b/transport-native-unix-common/src/main/java/io/netty/channel/unix/Errors.java similarity index 100% rename from transport-native-epoll/src/main/java/io/netty/channel/unix/Errors.java rename to transport-native-unix-common/src/main/java/io/netty/channel/unix/Errors.java diff --git a/transport-native-epoll/src/main/java/io/netty/channel/unix/ErrorsStaticallyReferencedJniMethods.java b/transport-native-unix-common/src/main/java/io/netty/channel/unix/ErrorsStaticallyReferencedJniMethods.java similarity index 100% rename from transport-native-epoll/src/main/java/io/netty/channel/unix/ErrorsStaticallyReferencedJniMethods.java rename to transport-native-unix-common/src/main/java/io/netty/channel/unix/ErrorsStaticallyReferencedJniMethods.java diff --git a/transport-native-epoll/src/main/java/io/netty/channel/unix/FileDescriptor.java b/transport-native-unix-common/src/main/java/io/netty/channel/unix/FileDescriptor.java similarity index 97% rename from transport-native-epoll/src/main/java/io/netty/channel/unix/FileDescriptor.java rename to transport-native-unix-common/src/main/java/io/netty/channel/unix/FileDescriptor.java index 190953c0a4..cd302db880 100644 --- a/transport-native-epoll/src/main/java/io/netty/channel/unix/FileDescriptor.java +++ b/transport-native-unix-common/src/main/java/io/netty/channel/unix/FileDescriptor.java @@ -25,6 +25,9 @@ import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import static io.netty.channel.unix.Errors.ioResult; import static io.netty.channel.unix.Errors.newIOException; +import static io.netty.channel.unix.LimitsStaticallyReferencedJniMethods.iovMax; +import static io.netty.channel.unix.LimitsStaticallyReferencedJniMethods.ssizeMax; +import static io.netty.channel.unix.LimitsStaticallyReferencedJniMethods.uioMaxIov; import static io.netty.util.internal.ObjectUtil.checkNotNull; /** @@ -72,6 +75,7 @@ public class FileDescriptor { private static final int STATE_ALL_MASK = STATE_CLOSED_MASK | STATE_INPUT_SHUTDOWN_MASK | STATE_OUTPUT_SHUTDOWN_MASK; + /** * Bit map = [Output Shutdown | Input Shutdown | Closed] */ @@ -88,7 +92,7 @@ public class FileDescriptor { /** * Return the int value of the filedescriptor. */ - public int intValue() { + public final int intValue() { return fd; } diff --git a/transport-native-epoll/src/main/java/io/netty/channel/epoll/IovArray.java b/transport-native-unix-common/src/main/java/io/netty/channel/unix/IovArray.java similarity index 89% rename from transport-native-epoll/src/main/java/io/netty/channel/epoll/IovArray.java rename to transport-native-unix-common/src/main/java/io/netty/channel/unix/IovArray.java index e7ff07dce8..e474658564 100644 --- a/transport-native-epoll/src/main/java/io/netty/channel/epoll/IovArray.java +++ b/transport-native-unix-common/src/main/java/io/netty/channel/unix/IovArray.java @@ -13,15 +13,17 @@ * License for the specific language governing permissions and limitations * under the License. */ -package io.netty.channel.epoll; +package io.netty.channel.unix; import io.netty.buffer.ByteBuf; import io.netty.buffer.CompositeByteBuf; import io.netty.channel.ChannelOutboundBuffer.MessageProcessor; import io.netty.util.internal.PlatformDependent; - import java.nio.ByteBuffer; +import static io.netty.channel.unix.Limits.IOV_MAX; +import static io.netty.channel.unix.Limits.SSIZE_MAX; + /** * Represent an array of struct array and so can be passed directly over via JNI without the need to do any more * array copies. @@ -39,7 +41,7 @@ import java.nio.ByteBuffer; *
Efficient JNI programming IV: Wrapping native data objects. */ -final class IovArray implements MessageProcessor { +public final class IovArray implements MessageProcessor { /** The size of an address which should be 8 for 64 bits and 4 for 32 bits. */ private static final int ADDRESS_SIZE = PlatformDependent.addressSize(); @@ -51,20 +53,20 @@ final class IovArray implements MessageProcessor { private static final int IOV_SIZE = 2 * ADDRESS_SIZE; /** - * The needed memory to hold up to {@link Native#IOV_MAX} iov entries, where {@link Native#IOV_MAX} signified + * The needed memory to hold up to {@code IOV_MAX} iov entries, where {@code IOV_MAX} signified * the maximum number of {@code iovec} structs that can be passed to {@code writev(...)}. */ - private static final int CAPACITY = Native.IOV_MAX * IOV_SIZE; + private static final int CAPACITY = IOV_MAX * IOV_SIZE; private final long memoryAddress; private int count; private long size; - IovArray() { + public IovArray() { memoryAddress = PlatformDependent.allocateMemory(CAPACITY); } - void clear() { + public void clear() { count = 0; size = 0; } @@ -73,8 +75,8 @@ final class IovArray implements MessageProcessor { * Try to add the given {@link ByteBuf}. Returns {@code true} on success, * {@code false} otherwise. */ - boolean add(ByteBuf buf) { - if (count == Native.IOV_MAX) { + public boolean add(ByteBuf buf) { + if (count == IOV_MAX) { // No more room! return false; } @@ -101,7 +103,7 @@ final class IovArray implements MessageProcessor { final long baseOffset = memoryAddress(count++); final long lengthOffset = baseOffset + ADDRESS_SIZE; - if (Native.SSIZE_MAX - len < size) { + if (SSIZE_MAX - len < size) { // If the size + len will overflow an SSIZE_MAX we stop populate the IovArray. This is done as linux // not allow to write more bytes then SSIZE_MAX with one writev(...) call and so will // return 'EINVAL', which will raise an IOException. @@ -128,9 +130,9 @@ final class IovArray implements MessageProcessor { * Try to add the given {@link CompositeByteBuf}. Returns {@code true} on success, * {@code false} otherwise. */ - boolean add(CompositeByteBuf buf) { + public boolean add(CompositeByteBuf buf) { ByteBuffer[] buffers = buf.nioBuffers(); - if (count + buffers.length >= Native.IOV_MAX) { + if (count + buffers.length >= IOV_MAX) { // No more room! return false; } @@ -154,7 +156,7 @@ final class IovArray implements MessageProcessor { * Process the written iov entries. This will return the length of the iov entry on the given index if it is * smaller then the given {@code written} value. Otherwise it returns {@code -1}. */ - long processWritten(int index, long written) { + public long processWritten(int index, long written) { long baseOffset = memoryAddress(index); long lengthOffset = baseOffset + ADDRESS_SIZE; if (ADDRESS_SIZE == 8) { @@ -183,28 +185,28 @@ final class IovArray implements MessageProcessor { /** * Returns the number if iov entries. */ - int count() { + public int count() { return count; } /** * Returns the size in bytes */ - long size() { + public long size() { return size; } /** * Returns the {@code memoryAddress} for the given {@code offset}. */ - long memoryAddress(int offset) { + public long memoryAddress(int offset) { return memoryAddress + IOV_SIZE * offset; } /** * Release the {@link IovArray}. Once release further using of it may crash the JVM! */ - void release() { + public void release() { PlatformDependent.freeMemory(memoryAddress); } diff --git a/transport-native-unix-common/src/main/java/io/netty/channel/unix/Limits.java b/transport-native-unix-common/src/main/java/io/netty/channel/unix/Limits.java new file mode 100644 index 0000000000..1747fc81f9 --- /dev/null +++ b/transport-native-unix-common/src/main/java/io/netty/channel/unix/Limits.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.unix; + +import static io.netty.channel.unix.LimitsStaticallyReferencedJniMethods.iovMax; +import static io.netty.channel.unix.LimitsStaticallyReferencedJniMethods.sizeOfjlong; +import static io.netty.channel.unix.LimitsStaticallyReferencedJniMethods.ssizeMax; +import static io.netty.channel.unix.LimitsStaticallyReferencedJniMethods.uioMaxIov; + +public final class Limits { + public static final int IOV_MAX = iovMax(); + public static final int UIO_MAX_IOV = uioMaxIov(); + public static final long SSIZE_MAX = ssizeMax(); + + public static final int SIZEOF_JLONG = sizeOfjlong(); + + private Limits() { } +} diff --git a/transport-native-unix-common/src/main/java/io/netty/channel/unix/LimitsStaticallyReferencedJniMethods.java b/transport-native-unix-common/src/main/java/io/netty/channel/unix/LimitsStaticallyReferencedJniMethods.java new file mode 100644 index 0000000000..429f434151 --- /dev/null +++ b/transport-native-unix-common/src/main/java/io/netty/channel/unix/LimitsStaticallyReferencedJniMethods.java @@ -0,0 +1,37 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.unix; + +/** + * This class is necessary to break the following cyclic dependency: + *
    + *
  1. JNI_OnLoad
  2. + *
  3. JNI Calls FindClass because RegisterNatives (used to register JNI methods) requires a class
  4. + *
  5. FindClass loads the class, but static members variables of that class attempt to call a JNI method which has not + * yet been registered.
  6. + *
  7. java.lang.UnsatisfiedLinkError is thrown because native method has not yet been registered.
  8. + *
+ * Static members which call JNI methods must not be declared in this class! + */ +final class LimitsStaticallyReferencedJniMethods { + private LimitsStaticallyReferencedJniMethods() { } + + static native long ssizeMax(); + static native int iovMax(); + static native int uioMaxIov(); + static native int sizeOfjlong(); + static native int udsSunPathSize(); +} diff --git a/transport-native-epoll/src/main/java/io/netty/channel/unix/NativeInetAddress.java b/transport-native-unix-common/src/main/java/io/netty/channel/unix/NativeInetAddress.java similarity index 100% rename from transport-native-epoll/src/main/java/io/netty/channel/unix/NativeInetAddress.java rename to transport-native-unix-common/src/main/java/io/netty/channel/unix/NativeInetAddress.java diff --git a/transport-native-epoll/src/main/java/io/netty/channel/unix/PeerCredentials.java b/transport-native-unix-common/src/main/java/io/netty/channel/unix/PeerCredentials.java similarity index 59% rename from transport-native-epoll/src/main/java/io/netty/channel/unix/PeerCredentials.java rename to transport-native-unix-common/src/main/java/io/netty/channel/unix/PeerCredentials.java index 93c6259cfd..cb67f3e061 100644 --- a/transport-native-epoll/src/main/java/io/netty/channel/unix/PeerCredentials.java +++ b/transport-native-unix-common/src/main/java/io/netty/channel/unix/PeerCredentials.java @@ -16,6 +16,10 @@ package io.netty.channel.unix; +import io.netty.util.internal.UnstableApi; + +import static io.netty.util.internal.EmptyArrays.EMPTY_INTS; + /** * User credentials discovered for the peer unix domain socket. * @@ -23,18 +27,25 @@ package io.netty.channel.unix; * For details see: * SO_PEERCRED */ +@UnstableApi public final class PeerCredentials { private final int pid; private final int uid; - private final int gid; + private final int[] gids; // These values are set by JNI via Socket.peerCredentials() - PeerCredentials(int p, int u, int g) { + PeerCredentials(int p, int u, int... gids) { pid = p; uid = u; - gid = g; + this.gids = gids == null ? EMPTY_INTS : gids; } + /** + * Get the PID of the peer process. + *

+ * This is currently not populated on MacOS and BSD based systems. + * @return The PID of the peer process. + */ public int pid() { return pid; } @@ -43,16 +54,21 @@ public final class PeerCredentials { return uid; } - public int gid() { - return gid; + public int[] gids() { + return gids.clone(); } @Override public String toString() { - return "UserCredentials[" - + "pid=" + pid - + "; uid=" + uid - + "; gid=" + gid - + "]"; + StringBuilder sb = new StringBuilder(128); + sb.append("UserCredentials[pid=").append(pid).append("; uid=").append(uid).append("; gids=["); + if (gids.length > 0) { + sb.append(gids[0]); + for (int i = 1; i < gids.length; ++i) { + sb.append(", ").append(gids[i]); + } + } + sb.append(']'); + return sb.toString(); } } diff --git a/transport-native-epoll/src/main/java/io/netty/channel/unix/ServerDomainSocketChannel.java b/transport-native-unix-common/src/main/java/io/netty/channel/unix/ServerDomainSocketChannel.java similarity index 100% rename from transport-native-epoll/src/main/java/io/netty/channel/unix/ServerDomainSocketChannel.java rename to transport-native-unix-common/src/main/java/io/netty/channel/unix/ServerDomainSocketChannel.java diff --git a/transport-native-epoll/src/main/java/io/netty/channel/unix/Socket.java b/transport-native-unix-common/src/main/java/io/netty/channel/unix/Socket.java similarity index 74% rename from transport-native-epoll/src/main/java/io/netty/channel/unix/Socket.java rename to transport-native-unix-common/src/main/java/io/netty/channel/unix/Socket.java index ccbc9abc19..a5739b564a 100644 --- a/transport-native-epoll/src/main/java/io/netty/channel/unix/Socket.java +++ b/transport-native-unix-common/src/main/java/io/netty/channel/unix/Socket.java @@ -30,8 +30,9 @@ import static io.netty.channel.unix.Errors.ERRNO_EAGAIN_NEGATIVE; import static io.netty.channel.unix.Errors.ERRNO_EINPROGRESS_NEGATIVE; import static io.netty.channel.unix.Errors.ERRNO_EWOULDBLOCK_NEGATIVE; import static io.netty.channel.unix.Errors.ioResult; -import static io.netty.channel.unix.Errors.throwConnectException; import static io.netty.channel.unix.Errors.newIOException; +import static io.netty.channel.unix.Errors.throwConnectException; +import static io.netty.channel.unix.LimitsStaticallyReferencedJniMethods.udsSunPathSize; import static io.netty.channel.unix.NativeInetAddress.address; import static io.netty.channel.unix.NativeInetAddress.ipv4MappedIpv6Address; import static io.netty.util.internal.ThrowableUtil.unknownStackTrace; @@ -40,7 +41,7 @@ import static io.netty.util.internal.ThrowableUtil.unknownStackTrace; * Provides a JNI bridge to native socket operations. * Internal usage only! */ -public final class Socket extends FileDescriptor { +public class Socket extends FileDescriptor { private static final ClosedChannelException SHUTDOWN_CLOSED_CHANNEL_EXCEPTION = unknownStackTrace( new ClosedChannelException(), Socket.class, "shutdown(..)"); private static final ClosedChannelException SEND_TO_CLOSED_CHANNEL_EXCEPTION = unknownStackTrace( @@ -67,15 +68,18 @@ public final class Socket extends FileDescriptor { private static final Errors.NativeConnectException CONNECT_REFUSED_EXCEPTION = unknownStackTrace(new Errors.NativeConnectException("syscall:connect", Errors.ERROR_ECONNREFUSED_NEGATIVE), Socket.class, "connect(..)"); + + public static final int UDS_SUN_PATH_SIZE = udsSunPathSize(); + public Socket(int fd) { super(fd); } - public void shutdown() throws IOException { + public final void shutdown() throws IOException { shutdown(true, true); } - public void shutdown(boolean read, boolean write) throws IOException { + public final void shutdown(boolean read, boolean write) throws IOException { for (;;) { // We need to only shutdown what has not been shutdown yet, and if there is no change we should not // shutdown anything. This is because if the underlying FD is reused and we still have an object which @@ -107,20 +111,20 @@ public final class Socket extends FileDescriptor { } } - public boolean isShutdown() { + public final boolean isShutdown() { int state = this.state; return isInputShutdown(state) && isOutputShutdown(state); } - public boolean isInputShutdown() { + public final boolean isInputShutdown() { return isInputShutdown(state); } - public boolean isOutputShutdown() { + public final boolean isOutputShutdown() { return isOutputShutdown(state); } - public int sendTo(ByteBuffer buf, int pos, int limit, InetAddress addr, int port) throws IOException { + public final int sendTo(ByteBuffer buf, int pos, int limit, InetAddress addr, int port) throws IOException { // just duplicate the toNativeInetAddress code here to minimize object creation as this method is expected // to be called frequently byte[] address; @@ -140,7 +144,7 @@ public final class Socket extends FileDescriptor { return ioResult("sendTo", res, SEND_TO_CONNECTION_RESET_EXCEPTION, SEND_TO_CLOSED_CHANNEL_EXCEPTION); } - public int sendToAddress(long memoryAddress, int pos, int limit, InetAddress addr, int port) + public final int sendToAddress(long memoryAddress, int pos, int limit, InetAddress addr, int port) throws IOException { // just duplicate the toNativeInetAddress code here to minimize object creation as this method is expected // to be called frequently @@ -162,7 +166,7 @@ public final class Socket extends FileDescriptor { SEND_TO_ADDRESS_CONNECTION_RESET_EXCEPTION, SEND_TO_ADDRESS_CLOSED_CHANNEL_EXCEPTION); } - public int sendToAddresses(long memoryAddress, int length, InetAddress addr, int port) throws IOException { + public final int sendToAddresses(long memoryAddress, int length, InetAddress addr, int port) throws IOException { // just duplicate the toNativeInetAddress code here to minimize object creation as this method is expected // to be called frequently byte[] address; @@ -183,15 +187,43 @@ public final class Socket extends FileDescriptor { CONNECTION_RESET_EXCEPTION_SENDMSG, SEND_TO_ADDRESSES_CLOSED_CHANNEL_EXCEPTION); } - public DatagramSocketAddress recvFrom(ByteBuffer buf, int pos, int limit) throws IOException { + public final DatagramSocketAddress recvFrom(ByteBuffer buf, int pos, int limit) throws IOException { return recvFrom(fd, buf, pos, limit); } - public DatagramSocketAddress recvFromAddress(long memoryAddress, int pos, int limit) throws IOException { + public final DatagramSocketAddress recvFromAddress(long memoryAddress, int pos, int limit) throws IOException { return recvFromAddress(fd, memoryAddress, pos, limit); } - public boolean connect(SocketAddress socketAddress) throws IOException { + public final int recvFd() throws IOException { + int res = recvFd(fd); + if (res > 0) { + return res; + } + if (res == 0) { + return -1; + } + + if (res == ERRNO_EAGAIN_NEGATIVE || res == ERRNO_EWOULDBLOCK_NEGATIVE) { + // Everything consumed so just return -1 here. + return 0; + } + throw newIOException("recvFd", res); + } + + public final int sendFd(int fdToSend) throws IOException { + int res = sendFd(fd, fdToSend); + if (res >= 0) { + return res; + } + if (res == ERRNO_EAGAIN_NEGATIVE || res == ERRNO_EWOULDBLOCK_NEGATIVE) { + // Everything consumed so just return -1 here. + return -1; + } + throw newIOException("sendFd", res); + } + + public final boolean connect(SocketAddress socketAddress) throws IOException { int res; if (socketAddress instanceof InetSocketAddress) { InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress; @@ -213,7 +245,7 @@ public final class Socket extends FileDescriptor { return true; } - public boolean finishConnect() throws IOException { + public final boolean finishConnect() throws IOException { int res = finishConnect(fd); if (res < 0) { if (res == ERRNO_EINPROGRESS_NEGATIVE) { @@ -225,7 +257,7 @@ public final class Socket extends FileDescriptor { return true; } - public void bind(SocketAddress socketAddress) throws IOException { + public final void bind(SocketAddress socketAddress) throws IOException { if (socketAddress instanceof InetSocketAddress) { InetSocketAddress addr = (InetSocketAddress) socketAddress; NativeInetAddress address = NativeInetAddress.newInstance(addr.getAddress()); @@ -244,14 +276,14 @@ public final class Socket extends FileDescriptor { } } - public void listen(int backlog) throws IOException { + public final void listen(int backlog) throws IOException { int res = listen(fd, backlog); if (res < 0) { throw newIOException("listen", res); } } - public int accept(byte[] addr) throws IOException { + public final int accept(byte[] addr) throws IOException { int res = accept(fd, addr); if (res >= 0) { return res; @@ -263,96 +295,94 @@ public final class Socket extends FileDescriptor { throw newIOException("accept", res); } - public InetSocketAddress remoteAddress() { + public final InetSocketAddress remoteAddress() { byte[] addr = remoteAddress(fd); // addr may be null if getpeername failed. // See https://github.com/netty/netty/issues/3328 - if (addr == null) { - return null; - } - return address(addr, 0, addr.length); + return addr == null ? null : address(addr, 0, addr.length); } - public InetSocketAddress localAddress() { + public final InetSocketAddress localAddress() { byte[] addr = localAddress(fd); // addr may be null if getpeername failed. // See https://github.com/netty/netty/issues/3328 - if (addr == null) { - return null; - } - return address(addr, 0, addr.length); + return addr == null ? null : address(addr, 0, addr.length); } - public int getReceiveBufferSize() throws IOException { + public final int getReceiveBufferSize() throws IOException { return getReceiveBufferSize(fd); } - public int getSendBufferSize() throws IOException { + public final int getSendBufferSize() throws IOException { return getSendBufferSize(fd); } - public boolean isKeepAlive() throws IOException { + public final boolean isKeepAlive() throws IOException { return isKeepAlive(fd) != 0; } - public boolean isTcpNoDelay() throws IOException { + public final boolean isTcpNoDelay() throws IOException { return isTcpNoDelay(fd) != 0; } - public boolean isTcpCork() throws IOException { - return isTcpCork(fd) != 0; + public final boolean isReuseAddress() throws IOException { + return isReuseAddress(fd) != 0; } - public int getSoLinger() throws IOException { + public final boolean isReusePort() throws IOException { + return isReusePort(fd) != 0; + } + + public final boolean isBroadcast() throws IOException { + return isBroadcast(fd) != 0; + } + + public final int getSoLinger() throws IOException { return getSoLinger(fd); } - public int getTcpDeferAccept() throws IOException { - return getTcpDeferAccept(fd); - } - - public boolean isTcpQuickAck() throws IOException { - return isTcpQuickAck(fd) != 0; - } - - public int getSoError() throws IOException { + public final int getSoError() throws IOException { return getSoError(fd); } - public PeerCredentials getPeerCredentials() throws IOException { - return getPeerCredentials(fd); + public final int getTrafficClass() throws IOException { + return getTrafficClass(fd); } - public void setKeepAlive(boolean keepAlive) throws IOException { + public final void setKeepAlive(boolean keepAlive) throws IOException { setKeepAlive(fd, keepAlive ? 1 : 0); } - public void setReceiveBufferSize(int receiveBufferSize) throws IOException { + public final void setReceiveBufferSize(int receiveBufferSize) throws IOException { setReceiveBufferSize(fd, receiveBufferSize); } - public void setSendBufferSize(int sendBufferSize) throws IOException { + public final void setSendBufferSize(int sendBufferSize) throws IOException { setSendBufferSize(fd, sendBufferSize); } - public void setTcpNoDelay(boolean tcpNoDelay) throws IOException { + public final void setTcpNoDelay(boolean tcpNoDelay) throws IOException { setTcpNoDelay(fd, tcpNoDelay ? 1 : 0); } - public void setTcpCork(boolean tcpCork) throws IOException { - setTcpCork(fd, tcpCork ? 1 : 0); - } - - public void setSoLinger(int soLinger) throws IOException { + public final void setSoLinger(int soLinger) throws IOException { setSoLinger(fd, soLinger); } - public void setTcpDeferAccept(int deferAccept) throws IOException { - setTcpDeferAccept(fd, deferAccept); + public final void setReuseAddress(boolean reuseAddress) throws IOException { + setReuseAddress(fd, reuseAddress ? 1 : 0); } - public void setTcpQuickAck(boolean quickAck) throws IOException { - setTcpQuickAck(fd, quickAck ? 1 : 0); + public final void setReusePort(boolean reusePort) throws IOException { + setReusePort(fd, reusePort ? 1 : 0); + } + + public final void setBroadcast(boolean broadcast) throws IOException { + setBroadcast(fd, broadcast ? 1 : 0); + } + + public final void setTrafficClass(int trafficClass) throws IOException { + setTrafficClass(fd, trafficClass); } @Override @@ -363,27 +393,39 @@ public final class Socket extends FileDescriptor { } public static Socket newSocketStream() { + return new Socket(newSocketStream0()); + } + + public static Socket newSocketDgram() { + return new Socket(newSocketDgram0()); + } + + public static Socket newSocketDomain() { + return new Socket(newSocketDomain0()); + } + + protected static int newSocketStream0() { int res = newSocketStreamFd(); if (res < 0) { throw new ChannelException(newIOException("newSocketStream", res)); } - return new Socket(res); + return res; } - public static Socket newSocketDgram() { + protected static int newSocketDgram0() { int res = newSocketDgramFd(); if (res < 0) { throw new ChannelException(newIOException("newSocketDgram", res)); } - return new Socket(res); + return res; } - public static Socket newSocketDomain() { + protected static int newSocketDomain0() { int res = newSocketDomainFd(); if (res < 0) { throw new ChannelException(newIOException("newSocketDomain", res)); } - return new Socket(res); + return res; } private static native int shutdown(int fd, boolean read, boolean write); @@ -409,28 +451,31 @@ public final class Socket extends FileDescriptor { int fd, ByteBuffer buf, int pos, int limit) throws IOException; private static native DatagramSocketAddress recvFromAddress( int fd, long memoryAddress, int pos, int limit) throws IOException; + private static native int recvFd(int fd); + private static native int sendFd(int socketFd, int fd); private static native int newSocketStreamFd(); private static native int newSocketDgramFd(); private static native int newSocketDomainFd(); + private static native int isReuseAddress(int fd) throws IOException; + private static native int isReusePort(int fd) throws IOException; private static native int getReceiveBufferSize(int fd) throws IOException; private static native int getSendBufferSize(int fd) throws IOException; private static native int isKeepAlive(int fd) throws IOException; private static native int isTcpNoDelay(int fd) throws IOException; - private static native int isTcpCork(int fd) throws IOException; + private static native int isBroadcast(int fd) throws IOException; private static native int getSoLinger(int fd) throws IOException; private static native int getSoError(int fd) throws IOException; - private static native int getTcpDeferAccept(int fd) throws IOException; - private static native int isTcpQuickAck(int fd) throws IOException; - private static native PeerCredentials getPeerCredentials(int fd) throws IOException; + private static native int getTrafficClass(int fd) throws IOException; + private static native void setReuseAddress(int fd, int reuseAddress) throws IOException; + private static native void setReusePort(int fd, int reuseAddress) throws IOException; private static native void setKeepAlive(int fd, int keepAlive) throws IOException; private static native void setReceiveBufferSize(int fd, int receiveBufferSize) throws IOException; private static native void setSendBufferSize(int fd, int sendBufferSize) throws IOException; private static native void setTcpNoDelay(int fd, int tcpNoDelay) throws IOException; - private static native void setTcpCork(int fd, int tcpCork) throws IOException; private static native void setSoLinger(int fd, int soLinger) throws IOException; - private static native void setTcpDeferAccept(int fd, int deferAccept) throws IOException; - private static native void setTcpQuickAck(int fd, int quickAck) throws IOException; + private static native void setBroadcast(int fd, int broadcast) throws IOException; + private static native void setTrafficClass(int fd, int trafficClass) throws IOException; } diff --git a/transport-native-unix-common/src/main/java/io/netty/channel/unix/SocketWritableByteChannel.java b/transport-native-unix-common/src/main/java/io/netty/channel/unix/SocketWritableByteChannel.java new file mode 100644 index 0000000000..b539a6b6dd --- /dev/null +++ b/transport-native-unix-common/src/main/java/io/netty/channel/unix/SocketWritableByteChannel.java @@ -0,0 +1,79 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.unix; + +import io.netty.buffer.ByteBufAllocator; +import io.netty.util.internal.ObjectUtil; +import java.nio.channels.WritableByteChannel; + +public abstract class SocketWritableByteChannel implements WritableByteChannel { + private final FileDescriptor fd; + + protected SocketWritableByteChannel(FileDescriptor fd) { + this.fd = ObjectUtil.checkNotNull(fd, "fd"); + } + + @Override + public final int write(java.nio.ByteBuffer src) throws java.io.IOException { + final int written; + int position = src.position(); + int limit = src.limit(); + if (src.isDirect()) { + written = fd.write(src, position, src.limit()); + } else { + final int readableBytes = limit - position; + io.netty.buffer.ByteBuf buffer = null; + try { + if (readableBytes == 0) { + buffer = io.netty.buffer.Unpooled.EMPTY_BUFFER; + } else { + final ByteBufAllocator alloc = alloc(); + if (alloc.isDirectBufferPooled()) { + buffer = alloc.directBuffer(readableBytes); + } else { + buffer = io.netty.buffer.ByteBufUtil.threadLocalDirectBuffer(); + if (buffer == null) { + buffer = io.netty.buffer.Unpooled.directBuffer(readableBytes); + } + } + } + buffer.writeBytes(src.duplicate()); + java.nio.ByteBuffer nioBuffer = buffer.internalNioBuffer(buffer.readerIndex(), readableBytes); + written = fd.write(nioBuffer, nioBuffer.position(), nioBuffer.limit()); + } finally { + if (buffer != null) { + buffer.release(); + } + } + } + if (written > 0) { + src.position(position + written); + } + return written; + } + + @Override + public final boolean isOpen() { + return fd.isOpen(); + } + + @Override + public final void close() throws java.io.IOException { + fd.close(); + } + + protected abstract ByteBufAllocator alloc(); +} diff --git a/transport-native-epoll/src/main/java/io/netty/channel/unix/UnixChannel.java b/transport-native-unix-common/src/main/java/io/netty/channel/unix/UnixChannel.java similarity index 100% rename from transport-native-epoll/src/main/java/io/netty/channel/unix/UnixChannel.java rename to transport-native-unix-common/src/main/java/io/netty/channel/unix/UnixChannel.java diff --git a/transport-native-unix-common/src/main/java/io/netty/channel/unix/UnixChannelOption.java b/transport-native-unix-common/src/main/java/io/netty/channel/unix/UnixChannelOption.java new file mode 100644 index 0000000000..4e5db77fe4 --- /dev/null +++ b/transport-native-unix-common/src/main/java/io/netty/channel/unix/UnixChannelOption.java @@ -0,0 +1,29 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.channel.unix; + +import io.netty.channel.ChannelOption; + +public class UnixChannelOption extends ChannelOption { + public static final ChannelOption SO_REUSEPORT = valueOf(UnixChannelOption.class, "SO_REUSEPORT"); + public static final ChannelOption DOMAIN_SOCKET_READ_MODE = + ChannelOption.valueOf(UnixChannelOption.class, "DOMAIN_SOCKET_READ_MODE"); + + @SuppressWarnings({ "unused", "deprecation" }) + protected UnixChannelOption() { + super(null); + } +} diff --git a/transport-native-epoll/src/main/java/io/netty/channel/unix/package-info.java b/transport-native-unix-common/src/main/java/io/netty/channel/unix/package-info.java similarity index 100% rename from transport-native-epoll/src/main/java/io/netty/channel/unix/package-info.java rename to transport-native-unix-common/src/main/java/io/netty/channel/unix/package-info.java diff --git a/transport/src/main/java/io/netty/bootstrap/ServerBootstrap.java b/transport/src/main/java/io/netty/bootstrap/ServerBootstrap.java index 34df324fc1..2a4c04e741 100644 --- a/transport/src/main/java/io/netty/bootstrap/ServerBootstrap.java +++ b/transport/src/main/java/io/netty/bootstrap/ServerBootstrap.java @@ -267,7 +267,7 @@ public class ServerBootstrap extends AbstractBootstrap