/* * Released into the public domain * with no warranty of any kind, either expressed or implied. */ package org.klomp.snark; import java.util.Map; import java.util.Properties; import net.i2p.client.I2PSession; import net.i2p.client.streaming.I2PSocketManager; import net.i2p.util.Log; import net.i2p.util.SimpleTimer2; /** * Periodically check for idle condition based on connected peers, * and reduce/restore tunnel count as necessary. * We can't use the I2CP idle detector because it's based on traffic, * so DHT and announces would keep it non-idle. * * @since 0.9.7 */ class IdleChecker extends SimpleTimer2.TimedEvent { private final SnarkManager _mgr; private final I2PSnarkUtil _util; private final PeerCoordinatorSet _pcs; private final Log _log; private int _consec; private int _consecNotRunning; private boolean _isIdle; private String _lastIn = "3"; private String _lastOut = "3"; private final Object _lock = new Object(); private static final long CHECK_TIME = 63*1000; private static final int MAX_CONSEC_IDLE = 4; private static final int MAX_CONSEC_NOT_RUNNING = 20; /** * Caller must schedule */ public IdleChecker(SnarkManager mgr, PeerCoordinatorSet pcs) { super(mgr.util().getContext().simpleTimer2()); _util = mgr.util(); _log = _util.getContext().logManager().getLog(IdleChecker.class); _mgr = mgr; _pcs = pcs; } public void timeReached() { synchronized (_lock) { locked_timeReached(); } } private void locked_timeReached() { if (_util.connected()) { boolean torrentRunning = false; int peerCount = 0; for (PeerCoordinator pc : _pcs) { if (!pc.halted()) { torrentRunning = true; peerCount += pc.getPeers(); } } if (torrentRunning) { _consecNotRunning = 0; } else { if (_consecNotRunning++ >= MAX_CONSEC_NOT_RUNNING) { if (_log.shouldLog(Log.WARN)) _log.warn("Closing tunnels on idle"); _util.disconnect(); _mgr.addMessage(_util.getString("No more torrents running.") + ' ' + _util.getString("I2P tunnel closed.")); schedule(3 * CHECK_TIME); return; } } if (peerCount > 0) { restoreTunnels(peerCount); } else { if (!_isIdle) { if (_consec++ >= MAX_CONSEC_IDLE) reduceTunnels(); else restoreTunnels(1); // pretend we have one peer for now } } } else { _isIdle = false; _consec = 0; _consecNotRunning = 0; _lastIn = "3"; _lastOut = "3"; } schedule(CHECK_TIME); } /** * Reduce to 1 in / 1 out tunnel */ private void reduceTunnels() { _isIdle = true; if (_log.shouldLog(Log.INFO)) _log.info("Reducing tunnels on idle"); setTunnels("1", "1", "0", "0"); } /** * Restore or adjust tunnel count based on current peer count * @param peerCount greater than zero */ private void restoreTunnels(int peerCount) { if (_isIdle && _log.shouldLog(Log.INFO)) _log.info("Restoring tunnels on activity"); _isIdle = false; Map<String, String> opts = _util.getI2CPOptions(); String i = opts.get("inbound.quantity"); if (i == null) i = Integer.toString(SnarkManager.DEFAULT_TUNNEL_QUANTITY); String o = opts.get("outbound.quantity"); if (o == null) o = Integer.toString(SnarkManager.DEFAULT_TUNNEL_QUANTITY); String ib = opts.get("inbound.backupQuantity"); if (ib == null) ib = "0"; String ob= opts.get("outbound.backupQuantity"); if (ob == null) ob = "0"; // we don't need more tunnels than we have peers, reduce if so // reduce to max(peerCount / 2, 2) int in, out; try { in = Integer.parseInt(i); } catch (NumberFormatException nfe) { in = 3; } try { out = Integer.parseInt(o); } catch (NumberFormatException nfe) { out = 3; } int target = Math.max(peerCount / 2, 2); if (target < in && in > 2) { in = target; i = Integer.toString(in); } if (target < out && out > 2) { out = target; o = Integer.toString(out); } if (!(_lastIn.equals(i) && _lastOut.equals(o))) setTunnels(i, o, ib, ob); } /** * Set in / out / in backup / out backup tunnel counts */ private void setTunnels(String i, String o, String ib, String ob) { _consec = 0; I2PSocketManager mgr = _util.getSocketManager(); if (mgr != null) { I2PSession sess = mgr.getSession(); if (sess != null) { if (_log.shouldLog(Log.INFO)) _log.info("New tunnel settings " + i + " / " + o + " / " + ib + " / " + ob); Properties newProps = new Properties(); newProps.setProperty("inbound.quantity", i); newProps.setProperty("outbound.quantity", o); newProps.setProperty("inbound.backupQuantity", ib); newProps.setProperty("outbound.backupQuantity", ob); sess.updateOptions(newProps); _lastIn = i; _lastOut = o; } } } }