Allow SshProxyServer to share AsynchronousChannelGroup 52/76452/1
authorRobert Varga <robert.varga@pantheon.tech>
Tue, 25 Sep 2018 17:51:33 +0000 (19:51 +0200)
committerRobert Varga <nite@hq.sk>
Wed, 26 Sep 2018 10:17:01 +0000 (10:17 +0000)
For testing purposes it is good to have the ability to share a single
thread group, as ach of them implies a dedicated thread. This patch
exposes an alternative constructor and allows multiple SshProxyServers
to share that group -- lowering resources needed significantly, both
in terms of threads, memory and file descriptors used.

Change-Id: I237ab0790e9a70c26288a116c59f5a541f236a74
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
netconf/netconf-ssh/src/main/java/org/opendaylight/netconf/ssh/SshProxyServer.java
netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/NetconfDeviceSimulator.java

index f638a2bfbf13e35e53adbdc8f2e800ffeb6e308e..4a5ea777bb3afa96e3768fe6ea2262c60bad7f42 100644 (file)
@@ -8,6 +8,9 @@
 
 package org.opendaylight.netconf.ssh;
 
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
 import io.netty.channel.EventLoopGroup;
 import java.io.IOException;
@@ -43,15 +46,30 @@ public class SshProxyServer implements AutoCloseable {
     private final EventLoopGroup clientGroup;
     private final IoServiceFactoryFactory nioServiceWithPoolFactoryFactory;
 
-    public SshProxyServer(final ScheduledExecutorService minaTimerExecutor,
-                          final EventLoopGroup clientGroup, final ExecutorService nioExecutor) {
+    private SshProxyServer(final ScheduledExecutorService minaTimerExecutor, final EventLoopGroup clientGroup,
+            final IoServiceFactoryFactory serviceFactory) {
         this.minaTimerExecutor = minaTimerExecutor;
         this.clientGroup = clientGroup;
-        this.nioServiceWithPoolFactoryFactory =
-                new NioServiceWithPoolFactory.NioServiceWithPoolFactoryFactory(nioExecutor);
+        this.nioServiceWithPoolFactoryFactory = serviceFactory;
         this.sshServer = SshServer.setUpDefaultServer();
     }
 
+    public SshProxyServer(final ScheduledExecutorService minaTimerExecutor,
+                          final EventLoopGroup clientGroup, final ExecutorService nioExecutor) {
+        this(minaTimerExecutor, clientGroup, new NioServiceWithPoolFactoryFactory(nioExecutor));
+    }
+
+    /**
+     * Create a server with a shared {@link AsynchronousChannelGroup}. Unlike the other constructor, this does
+     * not create a dedicated thread group, which is useful when you need to start a large number of servers and do
+     * not want to have a thread group (and hence an anonyous thread) for each of them.
+     */
+    @VisibleForTesting
+    public SshProxyServer(final ScheduledExecutorService minaTimerExecutor, final EventLoopGroup clientGroup,
+            final AsynchronousChannelGroup group) {
+        this(minaTimerExecutor, clientGroup, new SharedNioServiceFactoryFactory(group));
+    }
+
     public void bind(final SshProxyServerConfiguration sshProxyServerConfiguration) throws IOException {
         sshServer.setHost(sshProxyServerConfiguration.getBindingAddress().getHostString());
         sshServer.setPort(sshProxyServerConfiguration.getBindingAddress().getPort());
@@ -91,59 +109,85 @@ public class SshProxyServer implements AutoCloseable {
         }
     }
 
-    /**
-     * Based on Nio2ServiceFactory with one addition: injectable executor.
-     */
-    private static final class NioServiceWithPoolFactory extends AbstractCloseable implements IoServiceFactory {
-
+    private abstract static class AbstractNioServiceFactory extends AbstractCloseable implements IoServiceFactory {
         private final FactoryManager manager;
         private final AsynchronousChannelGroup group;
 
-        NioServiceWithPoolFactory(final FactoryManager manager, final ExecutorService executor) {
-            this.manager = manager;
-            try {
-                group = AsynchronousChannelGroup.withThreadPool(executor);
-            } catch (final IOException e) {
-                throw new RuntimeSshException(e);
-            }
+        AbstractNioServiceFactory(final FactoryManager manager, final AsynchronousChannelGroup group) {
+            this.manager = requireNonNull(manager);
+            this.group = requireNonNull(group);
+        }
+
+        final AsynchronousChannelGroup group() {
+            return group;
         }
 
         @Override
-        public IoConnector createConnector(final IoHandler handler) {
+        public final IoConnector createConnector(final IoHandler handler) {
             return new Nio2Connector(manager, handler, group);
         }
 
         @Override
-        public IoAcceptor createAcceptor(final IoHandler handler) {
+        public final IoAcceptor createAcceptor(final IoHandler handler) {
             return new Nio2Acceptor(manager, handler, group);
         }
+    }
+
+    /**
+     * Based on Nio2ServiceFactory with one addition: injectable executor.
+     */
+    private static final class NioServiceWithPoolFactory extends AbstractNioServiceFactory {
+        NioServiceWithPoolFactory(final FactoryManager manager, final AsynchronousChannelGroup group) {
+            super(manager, group);
+        }
 
         @SuppressWarnings("checkstyle:IllegalCatch")
         @Override
         protected void doCloseImmediately() {
             try {
-                group.shutdownNow();
-                group.awaitTermination(5, TimeUnit.SECONDS);
+                group().shutdownNow();
+                group().awaitTermination(5, TimeUnit.SECONDS);
             } catch (final Exception e) {
                 log.debug("Exception caught while closing channel group", e);
             } finally {
                 super.doCloseImmediately();
             }
         }
+    }
 
-        private static final class NioServiceWithPoolFactoryFactory extends Nio2ServiceFactoryFactory {
+    private static final class NioServiceWithPoolFactoryFactory extends Nio2ServiceFactoryFactory {
+        private final ExecutorService nioExecutor;
 
-            private final ExecutorService nioExecutor;
+        NioServiceWithPoolFactoryFactory(final ExecutorService nioExecutor) {
+            this.nioExecutor = nioExecutor;
+        }
 
-            private NioServiceWithPoolFactoryFactory(final ExecutorService nioExecutor) {
-                this.nioExecutor = nioExecutor;
+        @Override
+        public IoServiceFactory create(final FactoryManager manager) {
+            try {
+                return new NioServiceWithPoolFactory(manager, AsynchronousChannelGroup.withThreadPool(nioExecutor));
+            } catch (final IOException e) {
+                throw new RuntimeSshException("Failed to create channel group", e);
             }
+        }
+    }
 
-            @Override
-            public IoServiceFactory create(final FactoryManager manager) {
-                return new NioServiceWithPoolFactory(manager, nioExecutor);
-            }
+    private static final class SharedNioServiceFactory extends AbstractNioServiceFactory {
+        SharedNioServiceFactory(final FactoryManager manager, final AsynchronousChannelGroup group) {
+            super(manager, group);
         }
     }
 
+    private static final class SharedNioServiceFactoryFactory extends Nio2ServiceFactoryFactory {
+        private final AsynchronousChannelGroup group;
+
+        SharedNioServiceFactoryFactory(final AsynchronousChannelGroup group) {
+            this.group = requireNonNull(group);
+        }
+
+        @Override
+        public IoServiceFactory create(final FactoryManager manager) {
+            return new SharedNioServiceFactory(manager, group);
+        }
+    }
 }
index dea8fa819913cdb77b9ea2be0be32b4f99eeebee..5409e242d17c1cd6b168112b48989588dd237219 100644 (file)
@@ -28,6 +28,7 @@ import java.net.BindException;
 import java.net.Inet4Address;
 import java.net.InetSocketAddress;
 import java.net.UnknownHostException;
+import java.nio.channels.AsynchronousChannelGroup;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.List;
@@ -191,6 +192,13 @@ public class NetconfDeviceSimulator implements Closeable {
         // Generate key to temp folder
         final KeyPairProvider keyPairProvider = getPemGeneratorHostKeyProvider();
 
+        final AsynchronousChannelGroup group;
+        try {
+            group = AsynchronousChannelGroup.withThreadPool(nioExecutor);
+        } catch (IOException e) {
+            throw new IllegalStateException("Failed to create group", e);
+        }
+
         for (int i = 0; i < configuration.getDeviceCount(); i++) {
             if (currentPort > 65535) {
                 LOG.warn("Port cannot be greater than 65535, stopping further attempts.");
@@ -206,7 +214,7 @@ public class NetconfDeviceSimulator implements Closeable {
                 server = dispatcher.createLocalServer(tcpLocalAddress);
                 try {
                     final SshProxyServer sshServer = new SshProxyServer(
-                        minaTimerExecutor, nettyThreadgroup, nioExecutor);
+                        minaTimerExecutor, nettyThreadgroup, group);
                     sshServer.bind(getSshConfiguration(bindingAddress, tcpLocalAddress, keyPairProvider));
                     sshWrappers.add(sshServer);
                 } catch (final BindException e) {