diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java index ad5256fa65b4893ce578d05319d07bb6627bef01..1b7f4c926eb0c2c0d778ca142d38e4f307a697e5 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java @@ -42,8 +42,17 @@ public class TunnelController implements Logging { private final I2PTunnel _tunnel; private final List<String> _messages; private List<I2PSession> _sessions; - private boolean _running; - private boolean _starting; + private volatile TunnelState _state; + + private enum TunnelState { + START_ON_LOAD, + STARTING, + RUNNING, + STOPPING, + STOPPED, + DESTROYING, + DESTROYED, + } public static final String KEY_BACKUP_DIR = "i2ptunnel-keyBackup"; @@ -124,11 +133,10 @@ public class TunnelController implements Logging { _log = I2PAppContext.getGlobalContext().logManager().getLog(TunnelController.class); setConfig(config, prefix); _messages = new ArrayList<String>(4); - _running = false; boolean keyOK = true; if (createKey && (getType().endsWith("server") || getPersistentClientKey())) keyOK = createPrivateKey(); - _starting = keyOK && getStartOnLoad(); + _state = keyOK && getStartOnLoad() ? TunnelState.START_ON_LOAD : TunnelState.STOPPED; } /** @@ -193,8 +201,10 @@ public class TunnelController implements Logging { } public void startTunnelBackground() { - if (_running) return; - _starting = true; + synchronized (this) { + if (_state != TunnelState.STOPPED && _state != TunnelState.START_ON_LOAD) + return; + } new I2PAppThread(new Runnable() { public void run() { startTunnel(); } }).start(); } @@ -203,7 +213,17 @@ public class TunnelController implements Logging { * */ public void startTunnel() { - _starting = true; + synchronized (this) { + if (_state != TunnelState.STOPPED && _state != TunnelState.START_ON_LOAD) { + if (_state == TunnelState.RUNNING) { + if (_log.shouldLog(Log.INFO)) + _log.info("Already running"); + log("Tunnel " + getName() + " is already running"); + } + return; + } + changeState(TunnelState.STARTING); + } try { doStartTunnel(); } catch (Exception e) { @@ -213,21 +233,20 @@ public class TunnelController implements Logging { acquire(); stopTunnel(); } - _starting = false; } /** * @throws IllegalArgumentException via methods in I2PTunnel */ private void doStartTunnel() { - if (_running) { - if (_log.shouldLog(Log.INFO)) - _log.info("Already running"); - log("Tunnel " + getName() + " is already running"); - return; + synchronized (this) { + if (_state != TunnelState.STARTING) + return; } + String type = getType(); if ( (type == null) || (type.length() <= 0) ) { + changeState(TunnelState.STOPPED); if (_log.shouldLog(Log.ERROR)) _log.error("Cannot start the tunnel - no type specified"); return; @@ -237,6 +256,7 @@ public class TunnelController implements Logging { if (type.endsWith("server") || getPersistentClientKey()) { boolean ok = createPrivateKey(); if (!ok) { + changeState(TunnelState.STOPPED); log("Failed to start tunnel " + getName() + " as the private key file could not be created"); return; } @@ -268,12 +288,13 @@ public class TunnelController implements Logging { } else if (TYPE_STREAMR_SERVER.equals(type)) { startStreamrServer(); } else { + changeState(TunnelState.STOPPED); if (_log.shouldLog(Log.ERROR)) _log.error("Cannot start tunnel - unknown type [" + type + "]"); return; } acquire(); - _running = true; + changeState(TunnelState.RUNNING); } private void startHttpClient() { @@ -554,12 +575,17 @@ public class TunnelController implements Logging { * and it may have timer threads that continue running. */ public void stopTunnel() { + synchronized (this) { + if (_state != TunnelState.RUNNING) + return; + changeState(TunnelState.STOPPING); + } // I2PTunnel removes the session in close(), // so save the sessions to pass to release() and TCG Collection<I2PSession> sessions = getAllSessions(); _tunnel.runClose(new String[] { "forced", "all" }, this); release(sessions); - _running = false; + changeState(TunnelState.STOPPED); } /** @@ -569,12 +595,17 @@ public class TunnelController implements Logging { * @since 0.9.17 */ public void destroyTunnel() { + synchronized (this) { + if (_state != TunnelState.RUNNING) + return; + changeState(TunnelState.DESTROYING); + } // I2PTunnel removes the session in close(), // so save the sessions to pass to release() and TCG Collection<I2PSession> sessions = getAllSessions(); _tunnel.runClose(new String[] { "destroy", "all" }, this); release(sessions); - _running = false; + changeState(TunnelState.DESTROYED); } public void restartTunnel() { @@ -626,26 +657,29 @@ public class TunnelController implements Logging { // tell i2ptunnel, who will tell the TunnelTask, who will tell the SocketManager setSessionOptions(); - if (_running) { - Collection<I2PSession> sessions = getAllSessions(); - if (sessions.isEmpty()) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Running but no sessions to update"); - } - for (I2PSession s : sessions) { - // tell the router via the session - if (!s.isClosed()) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Session is open, updating: " + s); - s.updateOptions(_tunnel.getClientOptions()); - } else { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Session is closed, not updating: " + s); + synchronized (this) { + if (_state != TunnelState.RUNNING) { + if (_log.shouldLog(Log.DEBUG)) { + _log.debug("Not running, not updating sessions"); } + return; } - } else { - if (_log.shouldLog(Log.DEBUG)) { - _log.debug("Not running, not updating sessions"); + } + // Running, so check sessions + Collection<I2PSession> sessions = getAllSessions(); + if (sessions.isEmpty()) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Running but no sessions to update"); + } + for (I2PSession s : sessions) { + // tell the router via the session + if (!s.isClosed()) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Session is open, updating: " + s); + s.updateOptions(_tunnel.getClientOptions()); + } else { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Session is closed, not updating: " + s); } } } @@ -794,19 +828,26 @@ public class TunnelController implements Logging { return null; } - public boolean getIsRunning() { return _running; } - public boolean getIsStarting() { return _starting; } + public boolean getIsRunning() { return _state == TunnelState.RUNNING; } + public boolean getIsStarting() { return _state == TunnelState.START_ON_LOAD || _state == TunnelState.STARTING; } /** if running but no open sessions, we are in standby */ public boolean getIsStandby() { - if (!_running) - return false; + synchronized (this) { + if (_state != TunnelState.RUNNING) + return false; + } + for (I2PSession sess : _tunnel.getSessions()) { if (!sess.isClosed()) return false; } return true; } + + private synchronized void changeState(TunnelState state) { + _state = state; + } /** * A text description of the tunnel. @@ -927,7 +968,7 @@ public class TunnelController implements Logging { * */ public void log(String s) { - synchronized (this) { + synchronized (_messages) { _messages.add(s); while (_messages.size() > 10) _messages.remove(0); @@ -943,7 +984,7 @@ public class TunnelController implements Logging { */ public List<String> clearMessages() { List<String> rv = null; - synchronized (this) { + synchronized (_messages) { rv = new ArrayList<String>(_messages); _messages.clear(); } @@ -955,6 +996,6 @@ public class TunnelController implements Logging { */ @Override public String toString() { - return "TC " + getType() + ' ' + getName() + " for " + _tunnel; + return "TC " + getType() + ' ' + getName() + " for " + _tunnel + ' ' + _state; } }