This is an automated email from the ASF dual-hosted git repository.

twolf pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git


The following commit(s) were added to refs/heads/master by this push:
     new c1f31ee7c Avoid race condition when closing a SshClient
c1f31ee7c is described below

commit c1f31ee7c91db471605712ccc158a3a9f40f55b2
Author: Thomas Wolf <[email protected]>
AuthorDate: Mon Jan 5 00:07:07 2026 +0100

    Avoid race condition when closing a SshClient
    
    Cancellation of a session timeout task may occur asynchronously on
    closing sessions and on closing the client. Use AtomicReference to
    avoid a race condition and concurrent access to a non-volatile field.
---
 .../common/helpers/AbstractFactoryManager.java     | 34 ++++++++++------------
 1 file changed, 15 insertions(+), 19 deletions(-)

diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/helpers/AbstractFactoryManager.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/helpers/AbstractFactoryManager.java
index 82f1e0556..e5f99f752 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/common/helpers/AbstractFactoryManager.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/helpers/AbstractFactoryManager.java
@@ -29,6 +29,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Function;
 
 import org.apache.sshd.agent.SshAgentFactory;
@@ -83,8 +84,8 @@ public abstract class AbstractFactoryManager extends 
AbstractKexFactoryManager i
     protected FileSystemFactory fileSystemFactory;
     protected List<? extends ServiceFactory> serviceFactories;
     protected List<RequestHandler<ConnectionService>> globalRequestHandlers;
-    protected SessionTimeoutListener sessionTimeoutListener;
-    protected ScheduledFuture<?> timeoutListenerFuture;
+    protected final AtomicReference<SessionTimeoutListener> 
sessionTimeoutListener = new AtomicReference<>();
+    protected final AtomicReference<ScheduledFuture<?>> timeoutListenerFuture 
= new AtomicReference<>();
     protected final Collection<SessionListener> sessionListeners = new 
CopyOnWriteArraySet<>();
     protected final SessionListener sessionListenerProxy;
     protected final Collection<ChannelListener> channelListeners = new 
CopyOnWriteArraySet<>();
@@ -473,8 +474,9 @@ public abstract class AbstractFactoryManager extends 
AbstractKexFactoryManager i
     }
 
     protected void setupSessionTimeout(AbstractSessionFactory<?, ?> 
sessionFactory) {
-        sessionTimeoutListener = createSessionTimeoutListener();
-        addSessionListener(sessionTimeoutListener);
+        SessionTimeoutListener listener = createSessionTimeoutListener();
+        sessionTimeoutListener.set(listener);
+        addSessionListener(listener);
     }
 
     protected void removeSessionTimeout(AbstractSessionFactory<?, ?> 
sessionFactory) {
@@ -507,19 +509,16 @@ public abstract class AbstractFactoryManager extends 
AbstractKexFactoryManager i
     }
 
     private void ensureTimeoutScheduled() {
-        if (timeoutListenerFuture == null) {
-            timeoutListenerFuture = 
getScheduledExecutorService().scheduleAtFixedRate(sessionTimeoutListener, 1, 1,
-                    TimeUnit.SECONDS);
+        if (isOpen() && timeoutListenerFuture.get() == null) {
+            timeoutListenerFuture.set(
+                    
getScheduledExecutorService().scheduleAtFixedRate(sessionTimeoutListener.get(), 
1, 1, TimeUnit.SECONDS));
         }
     }
 
     private void cancelSessionTimeout() {
-        if (timeoutListenerFuture != null) {
-            try {
-                timeoutListenerFuture.cancel(true);
-            } finally {
-                timeoutListenerFuture = null;
-            }
+        ScheduledFuture<?> future = timeoutListenerFuture.getAndSet(null);
+        if (future != null) {
+            future.cancel(true);
         }
     }
 
@@ -528,12 +527,9 @@ public abstract class AbstractFactoryManager extends 
AbstractKexFactoryManager i
 
         // remove the sessionTimeoutListener completely; should the SSH 
server/client be restarted, a new one
         // will be created.
-        if (sessionTimeoutListener != null) {
-            try {
-                removeSessionListener(sessionTimeoutListener);
-            } finally {
-                sessionTimeoutListener = null;
-            }
+        SessionTimeoutListener listener = 
sessionTimeoutListener.getAndSet(null);
+        if (listener != null) {
+            removeSessionListener(listener);
         }
     }
 

Reply via email to