From a3fd28dcf4731272bd978cfc12481c59054733a6 Mon Sep 17 00:00:00 2001
From: zzz <zzz@i2pmail.org>
Date: Sun, 15 Jan 2023 10:02:17 -0500
Subject: [PATCH] i2ptunnel: Interrupt pending client tunnel build when stop
 button is clicked

Message/log cleanups
---
 .../i2p/i2ptunnel/I2PTunnelClientBase.java    | 99 +++++++++++--------
 1 file changed, 60 insertions(+), 39 deletions(-)

diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java
index b7e40f94da..164b8862f8 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java
@@ -69,6 +69,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
 
     private final Object startLock = new Object();
     private boolean startRunning;
+    private volatile boolean _buildingTunnels;
+    private volatile Thread _tunnelBuilder;
 
     // private Object closeLock = new Object();
 
@@ -467,42 +469,54 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
      * @since 0.9.20
      */
     private void connectManager() {
+        boolean closed = sockMgr.getSession().isClosed();
+        if (!closed)
+            return;
         int retries = 0;
-        while (sockMgr.getSession().isClosed()) {
-            try {
-                sockMgr.getSession().connect();
-                synchronized(I2PTunnelClientBase.class) {
-                    if (sockMgr == socketManager)
-                        _socketManagerState = SocketManagerState.CONNECTED;
-                }
-            } catch (I2PSessionException ise) {
-                // shadows instance _log
-                Log _log = getTunnel().getContext().logManager().getLog(I2PTunnelClientBase.class);
-                Logging log = this.l;
-                // try to make this error sensible as it will happen...
-                String portNum = getTunnel().port;
-                if (portNum == null)
-                    portNum = Integer.toString(I2PClient.DEFAULT_LISTEN_PORT);
-                String msg;
-                if (getTunnel().getContext().isRouterContext())
-                    msg = "Unable to build tunnels for the client";
-                else
-                    msg = "Unable to connect to the router at " + getTunnel().host + ':' + portNum +
-                             " and build tunnels for the client";
-                String exmsg = ise.getMessage();
-                boolean fail = exmsg != null && exmsg.contains("session limit exceeded");
-                if (!fail && ++retries < MAX_RETRIES) {
-                    if (log != null)
-                        log.log(msg + ", retrying in " + (RETRY_DELAY / 1000) + " seconds");
-                    _log.error(msg + ", retrying in " + (RETRY_DELAY / 1000) + " seconds", ise);
-                } else {
-                    if (log != null)
-                        log.log(msg + ", giving up");
-                    _log.log(Log.CRIT, msg + ", giving up", ise);
-                    throw new IllegalArgumentException(msg, ise);
+        _buildingTunnels = true;
+        _tunnelBuilder = Thread.currentThread();
+        try {
+            while (closed) {
+                try {
+                    sockMgr.getSession().connect();
+                    synchronized(I2PTunnelClientBase.class) {
+                        if (sockMgr == socketManager)
+                            _socketManagerState = SocketManagerState.CONNECTED;
+                    }
+                } catch (I2PSessionException ise) {
+                    // shadows instance _log
+                    Log _log = getTunnel().getContext().logManager().getLog(I2PTunnelClientBase.class);
+                    Logging log = this.l;
+                    // try to make this error sensible as it will happen...
+                    String portNum = getTunnel().port;
+                    if (portNum == null)
+                        portNum = Integer.toString(I2PClient.DEFAULT_LISTEN_PORT);
+                    String msg;
+                    if (getTunnel().getContext().isRouterContext())
+                        msg = "Unable to build tunnels for the client";
+                    else
+                        msg = "Unable to connect to the router at " + getTunnel().host + ':' + portNum +
+                                 " and build tunnels for the client";
+                    String exmsg = ise.getMessage();
+                    boolean fail = !_buildingTunnels || (exmsg != null && exmsg.contains("session limit exceeded"));
+                    if (!fail && ++retries < MAX_RETRIES) {
+                        if (log != null)
+                            log.log(msg + ", retrying in " + (RETRY_DELAY / 1000) + " seconds");
+                        _log.error(msg + ", retrying in " + (RETRY_DELAY / 1000) + " seconds", ise);
+                    } else {
+                        if (log != null)
+                            log.log(msg + ", giving up");
+                        _log.log(Log.CRIT, msg + ", giving up", ise);
+                        throw new IllegalArgumentException(msg, ise);
+                    }
+                    try { Thread.sleep(RETRY_DELAY); } catch (InterruptedException ie) { break; }
                 }
-                try { Thread.sleep(RETRY_DELAY); } catch (InterruptedException ie) {}
+                // _buildingTunnels set to false by close()
+                closed = _buildingTunnels && sockMgr.getSession().isClosed();
             }
+        } finally {
+            _buildingTunnels = false;
+            _tunnelBuilder = null;
         }
     }
 
@@ -543,9 +557,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
                     _log.error("Unable to connect to router and build tunnels for " + _handlerName);
                     // FIXME there is a loop in buildSocketManager(), do we really need another one here?
                     // no matter, buildSocketManager() now throws an IllegalArgumentException
-                    try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
-                } else {
-                    l.log("Tunnels ready for client: " + _handlerName);
+                    try { Thread.sleep(10*1000); } catch (InterruptedException ie) { return; }
                 }
             }
             // can't be null unless we limit the loop above
@@ -579,10 +591,12 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
         if (open && listenerReady) {
             if (localPort > 0) {  // -1 for I2Ping
                 boolean openNow = !Boolean.parseBoolean(getTunnel().getClientOptions().getProperty("i2cp.delayOpen"));
-                if (openNow || chained)
+                if (openNow || chained) {
                     l.log("Client ready, listening on " + getTunnel().listenHost + ':' + localPort);
-                else
+                    l.log("Tunnels ready for client: " + _handlerName);
+                } else {
                     l.log("Client ready, listening on " + getTunnel().listenHost + ':' + localPort + ", delaying tunnel open until required");
+                }
             }
             notifyEvent("openBaseClientResult", "ok");
         } else {
@@ -847,7 +861,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
                 // probably an IllegalArgumentException from
                 // connecting to the router in a delay-open or
                 // close-on-idle tunnel (in connectManager() above)
-                _log.error("Uncaught error in i2ptunnel client", t);
+                _log.error("i2ptunnel client error", t);
                 try { _s.close(); } catch (IOException ioe) {}
             }
         }
@@ -868,6 +882,13 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
     public boolean close(boolean forced) {
         if (_log.shouldLog(Log.INFO))
             _log.info("close() called: forced = " + forced + " open = " + open + " sockMgr = " + sockMgr);
+        if (forced) {
+            Thread t = _tunnelBuilder;
+            if (t != null) {
+                _buildingTunnels = false;
+                t.interrupt();
+            }
+        }
         if (!open) return true;
         // FIXME: here we might have to wait quite a long time if
         // there is a connection attempt atm. But without waiting we
-- 
GitLab