Skip to content

Commit c1f31ee

Browse files
committed
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.
1 parent c49c068 commit c1f31ee

1 file changed

Lines changed: 15 additions & 19 deletions

File tree

sshd-core/src/main/java/org/apache/sshd/common/helpers/AbstractFactoryManager.java

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.concurrent.ScheduledExecutorService;
3030
import java.util.concurrent.ScheduledFuture;
3131
import java.util.concurrent.TimeUnit;
32+
import java.util.concurrent.atomic.AtomicReference;
3233
import java.util.function.Function;
3334

3435
import org.apache.sshd.agent.SshAgentFactory;
@@ -83,8 +84,8 @@ public abstract class AbstractFactoryManager extends AbstractKexFactoryManager i
8384
protected FileSystemFactory fileSystemFactory;
8485
protected List<? extends ServiceFactory> serviceFactories;
8586
protected List<RequestHandler<ConnectionService>> globalRequestHandlers;
86-
protected SessionTimeoutListener sessionTimeoutListener;
87-
protected ScheduledFuture<?> timeoutListenerFuture;
87+
protected final AtomicReference<SessionTimeoutListener> sessionTimeoutListener = new AtomicReference<>();
88+
protected final AtomicReference<ScheduledFuture<?>> timeoutListenerFuture = new AtomicReference<>();
8889
protected final Collection<SessionListener> sessionListeners = new CopyOnWriteArraySet<>();
8990
protected final SessionListener sessionListenerProxy;
9091
protected final Collection<ChannelListener> channelListeners = new CopyOnWriteArraySet<>();
@@ -473,8 +474,9 @@ public void removePortForwardingEventListener(PortForwardingEventListener listen
473474
}
474475

475476
protected void setupSessionTimeout(AbstractSessionFactory<?, ?> sessionFactory) {
476-
sessionTimeoutListener = createSessionTimeoutListener();
477-
addSessionListener(sessionTimeoutListener);
477+
SessionTimeoutListener listener = createSessionTimeoutListener();
478+
sessionTimeoutListener.set(listener);
479+
addSessionListener(listener);
478480
}
479481

480482
protected void removeSessionTimeout(AbstractSessionFactory<?, ?> sessionFactory) {
@@ -507,19 +509,16 @@ public void sessionClosed(Session s) {
507509
}
508510

509511
private void ensureTimeoutScheduled() {
510-
if (timeoutListenerFuture == null) {
511-
timeoutListenerFuture = getScheduledExecutorService().scheduleAtFixedRate(sessionTimeoutListener, 1, 1,
512-
TimeUnit.SECONDS);
512+
if (isOpen() && timeoutListenerFuture.get() == null) {
513+
timeoutListenerFuture.set(
514+
getScheduledExecutorService().scheduleAtFixedRate(sessionTimeoutListener.get(), 1, 1, TimeUnit.SECONDS));
513515
}
514516
}
515517

516518
private void cancelSessionTimeout() {
517-
if (timeoutListenerFuture != null) {
518-
try {
519-
timeoutListenerFuture.cancel(true);
520-
} finally {
521-
timeoutListenerFuture = null;
522-
}
519+
ScheduledFuture<?> future = timeoutListenerFuture.getAndSet(null);
520+
if (future != null) {
521+
future.cancel(true);
523522
}
524523
}
525524

@@ -528,12 +527,9 @@ protected void stopSessionTimeoutListener(AbstractSessionFactory<?, ?> sessionFa
528527

529528
// remove the sessionTimeoutListener completely; should the SSH server/client be restarted, a new one
530529
// will be created.
531-
if (sessionTimeoutListener != null) {
532-
try {
533-
removeSessionListener(sessionTimeoutListener);
534-
} finally {
535-
sessionTimeoutListener = null;
536-
}
530+
SessionTimeoutListener listener = sessionTimeoutListener.getAndSet(null);
531+
if (listener != null) {
532+
removeSessionListener(listener);
537533
}
538534
}
539535

0 commit comments

Comments
 (0)