diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java index b94d2d3e4..6ee9eba64 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java @@ -37,6 +37,7 @@ import net.i2p.client.streaming.I2PServerSocket; import net.i2p.client.streaming.I2PSocket; import net.i2p.client.streaming.I2PSocketManager; import net.i2p.client.streaming.I2PSocketManagerFactory; +import net.i2p.client.streaming.RouterRestartException; import net.i2p.crypto.SigType; import net.i2p.data.Base64; import net.i2p.data.Hash; @@ -554,8 +555,8 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { I2PServerSocket ci2pss = i2pss; if (ci2pss == null) throw new I2PException("I2PServerSocket closed"); + // returns non-null as of 0.9.17 i2ps = ci2pss.accept(); - if (i2ps == null) throw new I2PException("I2PServerSocket closed"); if (_usePool) { try { _executor.execute(new Handler(i2ps)); @@ -573,10 +574,19 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { // use only for standard servers that can't get slowlorissed! Not for http or irc blockingHandle(i2ps); } + } catch (RouterRestartException rre) { + // Delay and loop if router is soft restarting + _log.logAlways(Log.WARN, "Waiting for router restart"); + if (i2ps != null) try { i2ps.close(); } catch (IOException ioe) {} + try { + Thread.sleep(2*60*1000); + } catch (InterruptedException ie) {} + // This should be the same as before, but we have to call getServerSocket() + // so sockMgr will call ConnectionManager.setAllowIncomingConnections(true) again + i2pss = sockMgr.getServerSocket(); } catch (I2PException ipe) { if (_log.shouldLog(Log.ERROR)) _log.error("Error accepting - KILLING THE TUNNEL SERVER", ipe); - // TODO delay and loop if internal router is soft restarting? open = false; if (i2ps != null) try { i2ps.close(); } catch (IOException ioe) {} break; diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocket.java b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocket.java index a421cf096..b2a6090b1 100644 --- a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocket.java +++ b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocket.java @@ -29,6 +29,7 @@ public interface I2PServerSocket { * * @throws I2PException if there is a problem with reading a new socket * from the data available (e.g. the I2PSession is closed) + * @throws RouterRestartException (extends I2PException) if the router is apparently restarting, since 0.9.34 * @throws ConnectException if the I2PServerSocket is closed, or if interrupted. * Not actually thrown through 0.9.16; thrown as of 0.9.17 * @throws SocketTimeoutException if a timeout was previously set with setSoTimeout and the timeout has been reached. diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/RouterRestartException.java b/apps/ministreaming/java/src/net/i2p/client/streaming/RouterRestartException.java new file mode 100644 index 000000000..0406a24dd --- /dev/null +++ b/apps/ministreaming/java/src/net/i2p/client/streaming/RouterRestartException.java @@ -0,0 +1,11 @@ +package net.i2p.client.streaming; + +import net.i2p.I2PException; + +/** + * An I2PException thrown from I2PServerSocket.accept() + * when the router is restarting. + * + * @since 0.9.34 + */ +public class RouterRestartException extends I2PException {} diff --git a/apps/streaming/java/src/net/i2p/client/streaming/impl/ConnectionHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/impl/ConnectionHandler.java index 7fc3c2fd1..213ee9c3a 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/impl/ConnectionHandler.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/impl/ConnectionHandler.java @@ -6,6 +6,7 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import net.i2p.I2PAppContext; +import net.i2p.client.streaming.RouterRestartException; import net.i2p.data.Destination; import net.i2p.util.Log; import net.i2p.util.SimpleTimer; @@ -27,6 +28,7 @@ class ConnectionHandler { private final SimpleTimer2 _timer; private volatile boolean _active; private int _acceptTimeout; + private boolean _restartPending; /** max time after receiveNewSyn() and before the matched accept() */ private static final int DEFAULT_ACCEPT_TIMEOUT = 3*1000; @@ -48,14 +50,27 @@ class ConnectionHandler { _acceptTimeout = DEFAULT_ACCEPT_TIMEOUT; } + /** + * The router told us it's going to restart. + * Call instead of setActive(false). + * + * @since 0.9.34 + */ + public synchronized void setRestartPending() { + _restartPending = true; + setActive(false); + } + public synchronized void setActive(boolean active) { // FIXME active=false this only kills for one thread in accept() // if there are more, they won't get a poison packet. if (_log.shouldLog(Log.WARN)) _log.warn("setActive(" + active + ") called, previously " + _active, new Exception("I did it")); // if starting, clear any old poison - if (active && !_active) + if (active && !_active) { + _restartPending = false; _synQueue.clear(); + } boolean wasActive = _active; _active = active; if (wasActive && !active) { @@ -116,12 +131,13 @@ class ConnectionHandler { * than 1ms, wait indefinitely) * @return connection received. Prior to 0.9.17, or null if there was a timeout or the * handler was shut down. As of 0.9.17, never null. + * @throws RouterRestartException (extends I2PException) if the router is apparently restarting, since 0.9.34 * @throws ConnectException since 0.9.17, returned null before; * if the I2PServerSocket is closed, or if interrupted. * @throws SocketTimeoutException since 0.9.17, returned null before; * if a timeout was previously set with setSoTimeout and the timeout has been reached. */ - public Connection accept(long timeoutMs) throws ConnectException, SocketTimeoutException { + public Connection accept(long timeoutMs) throws RouterRestartException, ConnectException, SocketTimeoutException { if (_log.shouldLog(Log.DEBUG)) _log.debug("Accept("+ timeoutMs+") called"); @@ -137,6 +153,8 @@ class ConnectionHandler { break; sendReset(packet); } + if (_restartPending) + throw new RouterRestartException(); throw new ConnectException("ServerSocket closed"); } @@ -174,8 +192,11 @@ class ConnectionHandler { } if (syn != null) { - if (syn.getOptionalDelay() == PoisonPacket.POISON_MAX_DELAY_REQUEST) + if (syn.getOptionalDelay() == PoisonPacket.POISON_MAX_DELAY_REQUEST) { + if (_restartPending) + throw new RouterRestartException(); throw new ConnectException("ServerSocket closed"); + } // deal with forged / invalid syn packets in _manager.receiveConnection() diff --git a/apps/streaming/java/src/net/i2p/client/streaming/impl/I2PServerSocketFull.java b/apps/streaming/java/src/net/i2p/client/streaming/impl/I2PServerSocketFull.java index 72146038e..28b615370 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/impl/I2PServerSocketFull.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/impl/I2PServerSocketFull.java @@ -30,6 +30,7 @@ class I2PServerSocketFull implements I2PServerSocket { * * @throws I2PException if there is a problem with reading a new socket * from the data available (e.g. the I2PSession is closed) + * @throws RouterRestartException (extends I2PException) if the router is apparently restarting, since 0.9.34 * @throws ConnectException if the I2PServerSocket is closed, or if interrupted. * Not actually thrown through 0.9.16; thrown as of 0.9.17 * @throws SocketTimeoutException if a timeout was previously set with setSoTimeout and the timeout has been reached. diff --git a/apps/streaming/java/src/net/i2p/client/streaming/impl/I2PSocketManagerFull.java b/apps/streaming/java/src/net/i2p/client/streaming/impl/I2PSocketManagerFull.java index ee4c4ecc7..88515b31d 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/impl/I2PSocketManagerFull.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/impl/I2PSocketManagerFull.java @@ -350,6 +350,7 @@ public class I2PSocketManagerFull implements I2PSocketManager { * * @return connected I2PSocket, or null through 0.9.16, non-null as of 0.9.17 * @throws I2PException if session is closed + * @throws RouterRestartException (extends I2PException) if the router is apparently restarting, since 0.9.34 * @throws ConnectException (since 0.9.17; I2PServerSocket interface always declared it) * @throws SocketTimeoutException if a timeout was previously set with setSoTimeout and the timeout has been reached. */ diff --git a/apps/streaming/java/src/net/i2p/client/streaming/impl/MessageHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/impl/MessageHandler.java index 234585c04..f5b2b89cf 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/impl/MessageHandler.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/impl/MessageHandler.java @@ -22,6 +22,7 @@ class MessageHandler implements I2PSessionMuxedListener { private final I2PAppContext _context; private final Log _log; private final Set _listeners; + private boolean _restartPending; public MessageHandler(I2PAppContext ctx, ConnectionManager mgr) { _manager = mgr; @@ -104,7 +105,12 @@ class MessageHandler implements I2PSessionMuxedListener { _log.warn("I2PSession disconnected"); _manager.disconnectAllHard(); // kill anybody waiting in accept() - _manager.getConnectionHandler().setActive(false); + if (_restartPending) { + _manager.getConnectionHandler().setRestartPending(); + _restartPending = false; + } else { + _manager.getConnectionHandler().setActive(false); + } for (I2PSocketManager.DisconnectListener lsnr : _listeners) { lsnr.sessionDisconnected(); @@ -120,8 +126,9 @@ class MessageHandler implements I2PSessionMuxedListener { * @param error the actual error */ public void errorOccurred(I2PSession session, String message, Throwable error) { + _restartPending = message.contains("restart"); if (_log.shouldLog(Log.WARN)) - _log.warn("error occurred: " + message + "- " + error.getMessage(), error); + _log.warn("error occurred: " + message, error); //_manager.disconnectAllHard(); } diff --git a/core/java/src/net/i2p/client/impl/DisconnectMessageHandler.java b/core/java/src/net/i2p/client/impl/DisconnectMessageHandler.java index 20582ddcb..7716dbb39 100644 --- a/core/java/src/net/i2p/client/impl/DisconnectMessageHandler.java +++ b/core/java/src/net/i2p/client/impl/DisconnectMessageHandler.java @@ -30,7 +30,7 @@ class DisconnectMessageHandler extends HandlerImpl { if (_log.shouldLog(Log.DEBUG)) _log.debug("Handle message " + message); String reason = ((DisconnectMessage)message).getReason(); - session.propogateError(reason, new I2PSessionException("Disconnect Message received")); + session.propogateError(reason, new I2PSessionException("Disconnect Message received: " + reason)); session.destroySession(false); if (reason.contains("restart")) { Thread t = new I2PAppThread(new Reconnector(session), "Reconnect " + session, true); @@ -47,7 +47,7 @@ class DisconnectMessageHandler extends HandlerImpl { } public void run() { - try { Thread.sleep(10*1000); } catch (InterruptedException ie) {} + try { Thread.sleep(40*1000); } catch (InterruptedException ie) {} _session.reconnect(); } } diff --git a/history.txt b/history.txt index 656cdcf64..f256f1363 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,12 @@ +2018-02-18 zzz + * i2ptunnel: Retry accept after router soft restart (ticket #2003) + +2018-02-17 zzz + * Console: Number formatting tweaks (ticket #1913) + * i2psnark: folder.js cleanup (ticket #2168, PR #14) + * i2ptunnel: Close sockets in finally{} + * SusiMail: Fix mail save truncation + 2018-02-16 zzz * i2psnark: Fix NPE on torrent not found (ticket #2167) * i2ptunnel: Change POST throttle response to 429 diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index c10128fe6..fecba78d6 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 6; + public final static long BUILD = 7; /** for example "-test" */ public final static String EXTRA = "";