You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2016/02/20 19:18:57 UTC

mina-sshd git commit: [SSHD-652] Allow pre-registering CloseFuture listeners without having to call "close" method

Repository: mina-sshd
Updated Branches:
  refs/heads/master d3e5d427e -> ddfe6f6b6


[SSHD-652] Allow pre-registering CloseFuture listeners without having to call "close" method


Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/ddfe6f6b
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/ddfe6f6b
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/ddfe6f6b

Branch: refs/heads/master
Commit: ddfe6f6b6de9c5ba3b798c319785d8304f794551
Parents: d3e5d42
Author: Lyor Goldstein <ly...@gmail.com>
Authored: Sat Feb 20 20:19:35 2016 +0200
Committer: Lyor Goldstein <ly...@gmail.com>
Committed: Sat Feb 20 20:19:35 2016 +0200

----------------------------------------------------------------------
 .../java/org/apache/sshd/common/Closeable.java  | 28 +++++++--
 .../sshd/common/channel/AbstractChannel.java    | 10 ++++
 .../org/apache/sshd/common/channel/Channel.java |  6 +-
 .../sshd/common/future/AbstractSshFuture.java   |  6 +-
 .../sshd/common/future/DefaultSshFuture.java    | 62 +++++++++++---------
 .../sshd/common/future/SshFutureListener.java   |  1 -
 .../apache/sshd/common/io/mina/MinaSession.java | 16 ++++-
 .../util/closeable/AbstractCloseable.java       | 12 ++++
 .../common/util/closeable/CloseableUtils.java   |  7 ---
 .../common/util/closeable/SimpleCloseable.java  | 11 ++++
 .../sshd/server/channel/ChannelSession.java     | 14 +++++
 .../common/session/AbstractSessionTest.java     | 33 ++++++++++-
 .../util/closeable/CloseableUtilsTest.java      | 31 ++++++++++
 .../sshd/server/channel/ChannelSessionTest.java | 25 ++++++++
 14 files changed, 216 insertions(+), 46 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/ddfe6f6b/sshd-core/src/main/java/org/apache/sshd/common/Closeable.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/Closeable.java b/sshd-core/src/main/java/org/apache/sshd/common/Closeable.java
index 8d7194a..a7278fc 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/Closeable.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/Closeable.java
@@ -21,11 +21,13 @@ package org.apache.sshd.common;
 import java.nio.channels.Channel;
 
 import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.future.SshFutureListener;
 
 /**
  * A {@code Closeable} is a resource that can be closed.
  * The close method is invoked to release resources that the object is
- * holding.
+ * holding. The user can pre-register listeners to be notified
+ * when resource close is completed (successfully or otherwise)
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
@@ -39,11 +41,28 @@ public interface Closeable extends Channel {
      *
      * @param immediately <code>true</code> if the resource should be shut down abruptly,
      *                    <code>false</code> for a graceful close
-     * @return a future
+     * @return a {@link CloseFuture} representing the close request
      */
     CloseFuture close(boolean immediately);
 
     /**
+     * Pre-register a listener to be informed when resource is closed. If
+     * resource is already closed, the listener will be invoked immediately
+     * and not registered for future notification
+     *
+     * @param listener The notification {@link #SshFutureListener} - never {@code null}
+     */
+    void addCloseFutureListener(SshFutureListener<CloseFuture> listener);
+
+    /**
+     * Remove a pre-registered close event listener
+     *
+     * @param listener The register {@link #SshFutureListener} - never {@code null}.
+     * Ignored if not registered or resource already closed
+     */
+    void removeCloseFutureListener(SshFutureListener<CloseFuture> listener);
+
+    /**
      * Returns <code>true</code> if this object has been closed.
      *
      * @return <code>true</code> if closing
@@ -52,9 +71,8 @@ public interface Closeable extends Channel {
 
     /**
      * Returns <code>true</code> if the {@link #close(boolean)} method
-     * has been called.
-     * Note that this method will return <code>true</code> even if
-     * this {@link #isClosed()} returns <code>true</code>.
+     * has been called. Note that this method will return <code>true</code>
+     * even if this {@link #isClosed()} returns <code>true</code>.
      *
      * @return <code>true</code> if closing
      */

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/ddfe6f6b/sshd-core/src/main/java/org/apache/sshd/common/channel/AbstractChannel.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/channel/AbstractChannel.java b/sshd-core/src/main/java/org/apache/sshd/common/channel/AbstractChannel.java
index 125e629..e560e54 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/channel/AbstractChannel.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/channel/AbstractChannel.java
@@ -455,6 +455,16 @@ public abstract class AbstractChannel
         }
 
         @Override
+        public void addCloseFutureListener(SshFutureListener<CloseFuture> listener) {
+            gracefulFuture.addListener(listener);
+        }
+
+        @Override
+        public void removeCloseFutureListener(SshFutureListener<CloseFuture> listener) {
+            gracefulFuture.removeListener(listener);
+        }
+
+        @Override
         public boolean isClosing() {
             return closing.get();
         }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/ddfe6f6b/sshd-core/src/main/java/org/apache/sshd/common/channel/Channel.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/channel/Channel.java b/sshd-core/src/main/java/org/apache/sshd/common/channel/Channel.java
index 0842953..dab6f72 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/channel/Channel.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/channel/Channel.java
@@ -34,7 +34,11 @@ import org.apache.sshd.common.util.buffer.Buffer;
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public interface Channel extends ChannelListenerManager, PropertyResolver, AttributeStore, Closeable {
+public interface Channel
+        extends ChannelListenerManager,
+                PropertyResolver,
+                AttributeStore,
+                Closeable {
     // Known types of channels
     String CHANNEL_EXEC = "exec";
     String CHANNEL_SHELL = "shell";

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/ddfe6f6b/sshd-core/src/main/java/org/apache/sshd/common/future/AbstractSshFuture.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/future/AbstractSshFuture.java b/sshd-core/src/main/java/org/apache/sshd/common/future/AbstractSshFuture.java
index 8efd65f..610b305 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/future/AbstractSshFuture.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/future/AbstractSshFuture.java
@@ -164,7 +164,11 @@ public abstract class AbstractSshFuture<T extends SshFuture> extends AbstractLog
         try {
             l.operationComplete(asT());
         } catch (Throwable t) {
-            log.warn("Listener threw an exception", t);
+            log.warn("notifyListener({}) failed ({}) to invoke {}: {}",
+                     this, t.getClass().getSimpleName(), l, t.getMessage());
+            if (log.isDebugEnabled()) {
+                log.debug("notifyListener(" + this + ")[" + l + "] invocation failure details", t);
+            }
         }
     }
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/ddfe6f6b/sshd-core/src/main/java/org/apache/sshd/common/future/DefaultSshFuture.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/future/DefaultSshFuture.java b/sshd-core/src/main/java/org/apache/sshd/common/future/DefaultSshFuture.java
index 8f31339..4c35210 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/future/DefaultSshFuture.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/future/DefaultSshFuture.java
@@ -119,21 +119,20 @@ public class DefaultSshFuture<T extends SshFuture> extends AbstractSshFuture<T>
         ValidateUtils.checkNotNull(listener, "Missing listener argument");
         boolean notifyNow = false;
         synchronized (lock) {
+            // if already have a result don't register the listener and invoke it directly
             if (result != null) {
                 notifyNow = true;
-            } else {
-                if (listeners == null) {
-                    listeners = listener;
-                } else if (listeners instanceof SshFutureListener) {
-                    listeners = new Object[]{listeners, listener};
-                } else {
-                    Object[] ol = (Object[]) listeners;
-                    int l = ol.length;
-                    Object[] nl = new Object[l + 1];
-                    System.arraycopy(ol, 0, nl, 0, l);
-                    nl[l] = listener;
-                    listeners = nl;
-                }
+            } else if (listeners == null) {
+                listeners = listener;   // 1st listener ?
+            } else if (listeners instanceof SshFutureListener) {
+                listeners = new Object[]{listeners, listener};
+            } else {    // increase array of registered listeners
+                Object[] ol = (Object[]) listeners;
+                int l = ol.length;
+                Object[] nl = new Object[l + 1];
+                System.arraycopy(ol, 0, nl, 0, l);
+                nl[l] = listener;
+                listeners = nl;
             }
         }
 
@@ -148,18 +147,22 @@ public class DefaultSshFuture<T extends SshFuture> extends AbstractSshFuture<T>
         ValidateUtils.checkNotNull(listener, "No listener provided");
 
         synchronized (lock) {
-            if (result == null) {
-                if (listeners != null) {
-                    if (listeners == listener) {
-                        listeners = null;
-                    } else {
-                        int l = Array.getLength(listeners);
-                        for (int i = 0; i < l; i++) {
-                            if (Array.get(listeners, i) == listener) {
-                                Array.set(listeners, i, null);
-                                break;
-                            }
-                        }
+            if (result != null) {
+                return asT();   // the train has already left the station...
+            }
+
+            if (listeners == null) {
+                return asT();   // no registered instances anyway
+            }
+
+            if (listeners == listener) {
+                listeners = null;   // the one and only
+            } else if (!(listeners instanceof SshFutureListener))  {
+                int l = Array.getLength(listeners);
+                for (int i = 0; i < l; i++) {
+                    if (Array.get(listeners, i) == listener) {
+                        Array.set(listeners, i, null);
+                        break;
                     }
                 }
             }
@@ -169,9 +172,12 @@ public class DefaultSshFuture<T extends SshFuture> extends AbstractSshFuture<T>
     }
 
     protected void notifyListeners() {
-        // There won't be any visibility problem or concurrent modification
-        // because 'ready' flag will be checked against both addListener and
-        // removeListener calls.
+        /*
+         * There won't be any visibility problem or concurrent modification
+         * because result value is checked in both addListener and
+         * removeListener calls under lock. If the result is already set then
+         * both methods will not modify the internal listeners
+         */
         if (listeners != null) {
             if (listeners instanceof SshFutureListener) {
                 notifyListener(asListener(listeners));

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/ddfe6f6b/sshd-core/src/main/java/org/apache/sshd/common/future/SshFutureListener.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/future/SshFutureListener.java b/sshd-core/src/main/java/org/apache/sshd/common/future/SshFutureListener.java
index ede2fcf..c17f551 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/future/SshFutureListener.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/future/SshFutureListener.java
@@ -38,5 +38,4 @@ public interface SshFutureListener<T extends SshFuture> extends EventListener {
      *               callback.
      */
     void operationComplete(T future);
-
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/ddfe6f6b/sshd-core/src/main/java/org/apache/sshd/common/io/mina/MinaSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/io/mina/MinaSession.java b/sshd-core/src/main/java/org/apache/sshd/common/io/mina/MinaSession.java
index dfd0bb2..24c26a6 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/io/mina/MinaSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/io/mina/MinaSession.java
@@ -25,7 +25,9 @@ import org.apache.mina.core.future.IoFuture;
 import org.apache.mina.core.future.IoFutureListener;
 import org.apache.mina.core.future.WriteFuture;
 import org.apache.sshd.common.Closeable;
+import org.apache.sshd.common.future.CloseFuture;
 import org.apache.sshd.common.future.DefaultCloseFuture;
+import org.apache.sshd.common.future.SshFutureListener;
 import org.apache.sshd.common.io.AbstractIoWriteFuture;
 import org.apache.sshd.common.io.IoService;
 import org.apache.sshd.common.io.IoSession;
@@ -90,6 +92,9 @@ public class MinaSession extends AbstractInnerCloseable implements IoSession {
     protected Closeable getInnerCloseable() {
         return new IoBaseCloseable() {
             @SuppressWarnings("synthetic-access")
+            private final DefaultCloseFuture future = new DefaultCloseFuture(lock);
+
+            @SuppressWarnings("synthetic-access")
             @Override
             public boolean isClosing() {
                 return session.isClosing();
@@ -101,10 +106,19 @@ public class MinaSession extends AbstractInnerCloseable implements IoSession {
                 return !session.isConnected();
             }
 
+            @Override
+            public void addCloseFutureListener(SshFutureListener<CloseFuture> listener) {
+                future.addListener(listener);
+            }
+
+            @Override
+            public void removeCloseFutureListener(SshFutureListener<CloseFuture> listener) {
+                future.removeListener(listener);
+            }
+
             @SuppressWarnings("synthetic-access")
             @Override
             public org.apache.sshd.common.future.CloseFuture close(boolean immediately) {
-                final DefaultCloseFuture future = new DefaultCloseFuture(lock);
                 session.close(false).addListener(new IoFutureListener<IoFuture>() {
                     @Override
                     public void operationComplete(IoFuture f) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/ddfe6f6b/sshd-core/src/main/java/org/apache/sshd/common/util/closeable/AbstractCloseable.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/closeable/AbstractCloseable.java b/sshd-core/src/main/java/org/apache/sshd/common/util/closeable/AbstractCloseable.java
index e32cf3d..c3780cb 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/closeable/AbstractCloseable.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/closeable/AbstractCloseable.java
@@ -40,10 +40,12 @@ public abstract class AbstractCloseable extends IoBaseCloseable {
      * Lock object for this session state
      */
     protected final Object lock = new Object();
+
     /**
      * State of this object
      */
     protected final AtomicReference<AbstractCloseable.State> state = new AtomicReference<>(State.Opened);
+
     /**
      * A future that will be set 'closed' when the object is actually closed
      */
@@ -58,6 +60,16 @@ public abstract class AbstractCloseable extends IoBaseCloseable {
     }
 
     @Override
+    public void addCloseFutureListener(SshFutureListener<CloseFuture> listener) {
+        closeFuture.addListener(listener);
+    }
+
+    @Override
+    public void removeCloseFutureListener(SshFutureListener<CloseFuture> listener) {
+        closeFuture.removeListener(listener);
+    }
+
+    @Override
     public CloseFuture close(boolean immediately) {
         if (immediately) {
             if (state.compareAndSet(State.Opened, State.Immediate)

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/ddfe6f6b/sshd-core/src/main/java/org/apache/sshd/common/util/closeable/CloseableUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/closeable/CloseableUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/util/closeable/CloseableUtils.java
index e0223c1..4d5d885 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/closeable/CloseableUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/closeable/CloseableUtils.java
@@ -26,7 +26,6 @@ import org.apache.sshd.common.Closeable;
 import org.apache.sshd.common.PropertyResolver;
 import org.apache.sshd.common.PropertyResolverUtils;
 import org.apache.sshd.common.future.CloseFuture;
-import org.apache.sshd.common.future.DefaultCloseFuture;
 
 /**
  * Utility class to help with {@link Closeable}s.
@@ -72,10 +71,4 @@ public final class CloseableUtils {
             }
         }
     }
-
-    public static CloseFuture closed() {
-        CloseFuture future = new DefaultCloseFuture(null);
-        future.setClosed();
-        return future;
-    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/ddfe6f6b/sshd-core/src/main/java/org/apache/sshd/common/util/closeable/SimpleCloseable.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/closeable/SimpleCloseable.java b/sshd-core/src/main/java/org/apache/sshd/common/util/closeable/SimpleCloseable.java
index 7bda0c6..8978972 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/closeable/SimpleCloseable.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/closeable/SimpleCloseable.java
@@ -22,6 +22,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.apache.sshd.common.future.CloseFuture;
 import org.apache.sshd.common.future.DefaultCloseFuture;
+import org.apache.sshd.common.future.SshFutureListener;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@@ -47,6 +48,16 @@ public class SimpleCloseable extends IoBaseCloseable {
     }
 
     @Override
+    public void addCloseFutureListener(SshFutureListener<CloseFuture> listener) {
+        future.addListener(listener);
+    }
+
+    @Override
+    public void removeCloseFutureListener(SshFutureListener<CloseFuture> listener) {
+        future.removeListener(listener);
+    }
+
+    @Override
     public CloseFuture close(boolean immediately) {
         if (closing.compareAndSet(false, true)) {
             doClose(immediately);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/ddfe6f6b/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java b/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
index 325b746..45a8f4f 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
@@ -123,6 +123,10 @@ public class ChannelSession extends AbstractServerChannel {
     }
 
     public class CommandCloseable extends IoBaseCloseable {
+        public CommandCloseable() {
+            super();
+        }
+
         @Override
         public boolean isClosed() {
             return commandExitFuture.isClosed();
@@ -134,6 +138,16 @@ public class ChannelSession extends AbstractServerChannel {
         }
 
         @Override
+        public void addCloseFutureListener(SshFutureListener<CloseFuture> listener) {
+            commandExitFuture.addListener(listener);
+        }
+
+        @Override
+        public void removeCloseFutureListener(SshFutureListener<CloseFuture> listener) {
+            commandExitFuture.removeListener(listener);
+        }
+
+        @Override
         public CloseFuture close(boolean immediately) {
             if (immediately || (commandInstance == null)) {
                 commandExitFuture.setClosed();

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/ddfe6f6b/sshd-core/src/test/java/org/apache/sshd/common/session/AbstractSessionTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/session/AbstractSessionTest.java b/sshd-core/src/test/java/org/apache/sshd/common/session/AbstractSessionTest.java
index 886f08f..da685f8 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/session/AbstractSessionTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/session/AbstractSessionTest.java
@@ -25,12 +25,15 @@ import java.util.Map;
 import java.util.Queue;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.sshd.common.FactoryManager;
 import org.apache.sshd.common.PropertyResolverUtils;
 import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.channel.IoWriteFutureImpl;
 import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.future.DefaultCloseFuture;
+import org.apache.sshd.common.future.SshFutureListener;
 import org.apache.sshd.common.io.IoService;
 import org.apache.sshd.common.io.IoSession;
 import org.apache.sshd.common.io.IoWriteFuture;
@@ -167,12 +170,37 @@ public class AbstractSessionTest extends BaseTestSupport {
         assertEquals("Mismatched number of ignore messages", Byte.SIZE, numIgnores);
     }
 
+    @Test   // see SSHD-652
+    public void testCloseFutureListenerRegistration() throws Exception {
+        final AtomicInteger closeCount = new AtomicInteger();
+        session.addCloseFutureListener(new SshFutureListener<CloseFuture>() {
+            @Override
+            public void operationComplete(CloseFuture future) {
+                assertTrue("Future not marted as closed", future.isClosed());
+                assertEquals("Unexpected multiple call to callback", 1, closeCount.incrementAndGet());
+            }
+        });
+        session.close();
+        assertEquals("Close listener not called", 1, closeCount.get());
+    }
+
     public static class MyIoSession implements IoSession {
         private final Queue<Buffer> outgoing = new LinkedBlockingQueue<>();
         private final AtomicBoolean open = new AtomicBoolean(true);
+        private final CloseFuture closeFuture;
 
         public MyIoSession() {
-            super();
+            closeFuture = new DefaultCloseFuture(open);
+        }
+
+        @Override
+        public void addCloseFutureListener(SshFutureListener<CloseFuture> listener) {
+            closeFuture.addListener(listener);
+        }
+
+        @Override
+        public void removeCloseFutureListener(SshFutureListener<CloseFuture> listener) {
+            closeFuture.addListener(listener);
         }
 
         public Queue<Buffer> getOutgoingMessages() {
@@ -242,9 +270,10 @@ public class AbstractSessionTest extends BaseTestSupport {
         public CloseFuture close(boolean immediately) {
             if (open.getAndSet(false)) {
                 outgoing.clear();
+                closeFuture.setClosed();
             }
 
-            return null;
+            return closeFuture;
         }
 
         @Override

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/ddfe6f6b/sshd-core/src/test/java/org/apache/sshd/common/util/closeable/CloseableUtilsTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/util/closeable/CloseableUtilsTest.java b/sshd-core/src/test/java/org/apache/sshd/common/util/closeable/CloseableUtilsTest.java
index 8d8d807..8605d2d 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/util/closeable/CloseableUtilsTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/util/closeable/CloseableUtilsTest.java
@@ -28,6 +28,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.sshd.common.Closeable;
 import org.apache.sshd.common.future.CloseFuture;
 import org.apache.sshd.common.future.DefaultCloseFuture;
+import org.apache.sshd.common.future.SshFutureListener;
 import org.apache.sshd.common.util.threads.ThreadUtils;
 import org.apache.sshd.util.test.BaseTestSupport;
 import org.junit.FixMethodOrder;
@@ -53,6 +54,16 @@ public class CloseableUtilsTest extends BaseTestSupport {
             }
 
             @Override
+            public void addCloseFutureListener(SshFutureListener<CloseFuture> listener) {
+                fail("Unexpected call to addCloseFutureListener");
+            }
+
+            @Override
+            public void removeCloseFutureListener(SshFutureListener<CloseFuture> listener) {
+                fail("Unexpected call to removeCloseFutureListener");
+            }
+
+            @Override
             public boolean isClosed() {
                 return true;
             }
@@ -75,6 +86,16 @@ public class CloseableUtilsTest extends BaseTestSupport {
             }
 
             @Override
+            public void addCloseFutureListener(SshFutureListener<CloseFuture> listener) {
+                fail("Unexpected call to addCloseFutureListener");
+            }
+
+            @Override
+            public void removeCloseFutureListener(SshFutureListener<CloseFuture> listener) {
+                fail("Unexpected call to removeCloseFutureListener");
+            }
+
+            @Override
             public boolean isClosed() {
                 return false;
             }
@@ -100,6 +121,16 @@ public class CloseableUtilsTest extends BaseTestSupport {
             }
 
             @Override
+            public void addCloseFutureListener(SshFutureListener<CloseFuture> listener) {
+                fail("Unexpected call to addCloseFutureListener");
+            }
+
+            @Override
+            public void removeCloseFutureListener(SshFutureListener<CloseFuture> listener) {
+                fail("Unexpected call to removeCloseFutureListener");
+            }
+
+            @Override
             public boolean isClosed() {
                 return false;
             }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/ddfe6f6b/sshd-core/src/test/java/org/apache/sshd/server/channel/ChannelSessionTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/channel/ChannelSessionTest.java b/sshd-core/src/test/java/org/apache/sshd/server/channel/ChannelSessionTest.java
index addfa75..a000104 100644
--- a/sshd-core/src/test/java/org/apache/sshd/server/channel/ChannelSessionTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/server/channel/ChannelSessionTest.java
@@ -21,10 +21,13 @@ package org.apache.sshd.server.channel;
 import java.io.IOException;
 import java.util.Collections;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.sshd.common.PropertyResolverUtils;
 import org.apache.sshd.common.channel.ChannelAsyncOutputStream;
 import org.apache.sshd.common.channel.Window;
+import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.future.SshFutureListener;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 import org.apache.sshd.util.test.BaseTestSupport;
@@ -65,4 +68,26 @@ public class ChannelSessionTest extends BaseTestSupport {
             assertTrue("Expanded ?", expanded.get());
         }
     }
+
+    @Test   // see SSHD-652
+    public void testCloseFutureListenerRegistration() throws Exception {
+        final AtomicInteger closeCount = new AtomicInteger();
+        try (ChannelSession session = new ChannelSession() {
+            {
+                Window wRemote = getRemoteWindow();
+                wRemote.init(PropertyResolverUtils.toPropertyResolver(Collections.<String,Object>emptyMap()));
+            }
+        }) {
+            session.addCloseFutureListener(new SshFutureListener<CloseFuture>() {
+                @Override
+                public void operationComplete(CloseFuture future) {
+                    assertTrue("Future not marted as closed", future.isClosed());
+                    assertEquals("Unexpected multiple call to callback", 1, closeCount.incrementAndGet());
+                }
+            });
+            session.close();
+        }
+
+        assertEquals("Close listener not called", 1, closeCount.get());
+    }
 }
\ No newline at end of file