BUG-2340 Fix improper cleanup of resources in netconf ssh handler
[controller.git] / opendaylight / netconf / netconf-netty-util / src / main / java / org / opendaylight / controller / netconf / nettyutil / handler / ssh / client / AsyncSshHandler.java
index 14d753f1f8b59c25a7ea871e3bfe5e430c1d9970..c8c912828279e72eab9ac12dba25e2a29e2c10d4 100644 (file)
@@ -137,7 +137,6 @@ public class AsyncSshHandler extends ChannelOutboundHandlerAdapter {
         LOG.trace("SSH subsystem channel opened successfully on channel: {}", ctx.channel());
 
         connectPromise.setSuccess();
-        connectPromise = null;
 
         // TODO we should also read from error stream and at least log from that
 
@@ -162,9 +161,12 @@ public class AsyncSshHandler extends ChannelOutboundHandlerAdapter {
 
     private synchronized void handleSshSetupFailure(final ChannelHandlerContext ctx, final Throwable e) {
         LOG.warn("Unable to setup SSH connection on channel: {}", ctx.channel(), e);
-        connectPromise.setFailure(e);
-        connectPromise = null;
-        throw new IllegalStateException("Unable to setup SSH connection on channel: " + ctx.channel(), e);
+        disconnect(ctx, ctx.newPromise());
+
+        // If the promise is not yet done, we have failed with initial connect and set connectPromise to failure
+        if(!connectPromise.isDone()) {
+            connectPromise.setFailure(e);
+        }
     }
 
     @Override
@@ -185,6 +187,15 @@ public class AsyncSshHandler extends ChannelOutboundHandlerAdapter {
 
     @Override
     public synchronized void disconnect(final ChannelHandlerContext ctx, final ChannelPromise promise) {
+        // Super disconnect is necessary in this case since we are using NioSocketChannel and it needs to cleanup its resources
+        // e.g. Socket that it tries to open in its constructor (https://bugs.opendaylight.org/show_bug.cgi?id=2430)
+        // TODO better solution would be to implement custom ChannelFactory + Channel that will use mina SSH lib internally: port this to custom channel implementation
+        try {
+            super.disconnect(ctx, ctx.newPromise());
+        } catch (final Exception e) {
+            LOG.warn("Unable to cleanup all resources for channel: {}. Ignoring.", ctx.channel(), e);
+        }
+
         if(sshReadAsyncListener != null) {
             sshReadAsyncListener.close();
         }
@@ -205,11 +216,15 @@ public class AsyncSshHandler extends ChannelOutboundHandlerAdapter {
             });
         }
 
+        // If we have already succeeded and the session was dropped after, we need to fire inactive to notify reconnect logic
+        if(connectPromise.isSuccess()) {
+            ctx.fireChannelInactive();
+        }
+
         channel = null;
-        promise.setSuccess();
 
+        promise.setSuccess();
         LOG.debug("SSH session closed on channel: {}", ctx.channel());
-        ctx.fireChannelInactive();
     }
 
 }