diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index c80f2b40a15..f87ca14e869 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -25,6 +25,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima * Prevented XML External Entity (XXE) style attacks via `GraphMLReader` by disabling DTD and external entities by default. * Fixed a `NullPointerException` that could occur during a failed `Connection` initialization due to uninstantiated `AtomicInteger`. +* Added a check for when Epoll is available in the Java client and if so use it instead of Nio. [[release-3-4-12]] === TinkerPop 3.4.12 (Release Date: July 19, 2021) diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Cluster.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Cluster.java index eaa5e99cb8d..91b8b0ea5b3 100644 --- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Cluster.java +++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Cluster.java @@ -20,6 +20,9 @@ import io.netty.buffer.PooledByteBufAllocator; import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.epoll.Epoll; +import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslProvider; @@ -1103,11 +1106,14 @@ public Cluster create() { } static class Factory { - private final NioEventLoopGroup group; + private final EventLoopGroup group; public Factory(final int nioPoolSize) { final BasicThreadFactory threadFactory = new BasicThreadFactory.Builder().namingPattern("gremlin-driver-loop-%d").build(); - group = new NioEventLoopGroup(nioPoolSize, threadFactory); + // Checks and uses Epoll if it is available. ref: http://netty.io/wiki/native-transports.html + System.out.println(Epoll.isAvailable() ? "epoll is available, using epoll" : "epoll is unavailable, using NIO."); + logger.warn(Epoll.isAvailable() ? "epoll is available, using epoll" : "epoll is unavailable, using NIO."); + group = Epoll.isAvailable() ? new EpollEventLoopGroup(nioPoolSize, threadFactory) : new NioEventLoopGroup(nioPoolSize, threadFactory); } Bootstrap createBootstrap() { diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/simple/AbstractClient.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/simple/AbstractClient.java index e991e3eaebb..ee5cd3fa6db 100644 --- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/simple/AbstractClient.java +++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/simple/AbstractClient.java @@ -21,11 +21,15 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.EventLoopGroup; import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.epoll.Epoll; +import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.apache.tinkerpop.gremlin.driver.message.RequestMessage; import org.apache.tinkerpop.gremlin.driver.message.ResponseMessage; import org.apache.tinkerpop.gremlin.driver.message.ResponseStatusCode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; @@ -38,10 +42,14 @@ public abstract class AbstractClient implements SimpleClient { protected final CallbackResponseHandler callbackResponseHandler = new CallbackResponseHandler(); protected final EventLoopGroup group; + protected static final Logger logger = LoggerFactory.getLogger(AbstractClient.class); public AbstractClient(final String threadPattern) { final BasicThreadFactory threadFactory = new BasicThreadFactory.Builder().namingPattern(threadPattern).build(); - group = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors(), threadFactory); + // Checks and uses Epoll if it is available. ref: http://netty.io/wiki/native-transports.html + logger.warn(Epoll.isAvailable() ? "epoll is available, using epoll" : "epoll is unavailable, using NIO."); + group = Epoll.isAvailable() ? new EpollEventLoopGroup(Runtime.getRuntime().availableProcessors(), threadFactory) + : new NioEventLoopGroup(Runtime.getRuntime().availableProcessors(), threadFactory); } public abstract void writeAndFlush(final RequestMessage requestMessage) throws Exception; diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/simple/WebSocketClient.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/simple/WebSocketClient.java index 8ef48b6786b..57db7543826 100644 --- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/simple/WebSocketClient.java +++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/simple/WebSocketClient.java @@ -20,6 +20,8 @@ import io.netty.buffer.PooledByteBufAllocator; import io.netty.channel.ChannelOption; +import io.netty.channel.epoll.Epoll; +import io.netty.channel.epoll.EpollSocketChannel; import io.netty.handler.codec.http.EmptyHttpHeaders; import org.apache.tinkerpop.gremlin.driver.MessageSerializer; import org.apache.tinkerpop.gremlin.driver.handler.WebSocketClientHandler; @@ -66,7 +68,9 @@ public WebSocketClient(final URI uri) { final WebSocketClientHandler wsHandler = new WebSocketClientHandler(WebSocketClientHandshakerFactory.newHandshaker( uri, WebSocketVersion.V13, null, true, EmptyHttpHeaders.INSTANCE, 65536), 10000); final MessageSerializer serializer = new GryoMessageSerializerV3d0(); - b.channel(NioSocketChannel.class) + // Checks and uses Epoll if it is available. ref: http://netty.io/wiki/native-transports.html + logger.info(Epoll.isAvailable() ? "epoll is available, using epoll" : "epoll is unavailable, using NIO."); + b.channel(Epoll.isAvailable() ? EpollSocketChannel.class : NioSocketChannel.class) .handler(new ChannelInitializer() { @Override protected void initChannel(final SocketChannel ch) { diff --git a/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/SimpleSocketServer.java b/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/SimpleSocketServer.java index 84eaf4a5344..2abc69ca53e 100644 --- a/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/SimpleSocketServer.java +++ b/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/SimpleSocketServer.java @@ -22,6 +22,9 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; +import io.netty.channel.epoll.Epoll; +import io.netty.channel.epoll.EpollEventLoopGroup; +import io.netty.channel.epoll.EpollServerSocketChannel; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; @@ -37,11 +40,12 @@ public class SimpleSocketServer { private EventLoopGroup workerGroup; public Channel start(final ChannelInitializer channelInitializer) throws InterruptedException { - bossGroup = new NioEventLoopGroup(1); - workerGroup = new NioEventLoopGroup(); + // Checks and uses Epoll if it is available. ref: http://netty.io/wiki/native-transports.html + bossGroup = Epoll.isAvailable() ? new EpollEventLoopGroup(1) : new NioEventLoopGroup(1); + workerGroup = Epoll.isAvailable() ? new EpollEventLoopGroup() : new NioEventLoopGroup(); final ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) - .channel(NioServerSocketChannel.class) + .channel(Epoll.isAvailable() ? EpollServerSocketChannel.class : NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(channelInitializer); return b.bind(PORT).sync().channel(); diff --git a/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/WebSocketClientBehaviorIntegrateTest.java b/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/WebSocketClientBehaviorIntegrateTest.java index 0eb34f6867e..5a643d02d81 100644 --- a/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/WebSocketClientBehaviorIntegrateTest.java +++ b/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/WebSocketClientBehaviorIntegrateTest.java @@ -18,6 +18,11 @@ */ package org.apache.tinkerpop.gremlin.driver; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.epoll.Epoll; +import io.netty.channel.epoll.EpollEventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import org.apache.tinkerpop.gremlin.driver.simple.WebSocketClient; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -41,6 +46,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; public class WebSocketClientBehaviorIntegrateTest { @Rule @@ -271,4 +277,22 @@ public void shouldNotCreateReplacementConnectionWhenClientClosesConnection() thr .filter(str -> str.contains("Considering new connection on")) .count()); } -} \ No newline at end of file + + // A mock client is made here, so we can test the protected field group + private static class MockClient extends WebSocketClient { + EventLoopGroup getEventLoopGroup() { + return group; + } + } + + @Test + public void shouldUseEpollIfAvailable() throws Exception { + final MockClient client = new MockClient(); + if (Epoll.isAvailable()) { + assertTrue(client.getEventLoopGroup() instanceof EpollEventLoopGroup); + } else { + assertTrue(client.getEventLoopGroup() instanceof NioEventLoopGroup); + } + } + +}