From f14ff31a2072e58f25e5deaac815cf351e239478 Mon Sep 17 00:00:00 2001 From: zzz Date: Wed, 30 May 2012 20:03:30 +0000 Subject: [PATCH] * Timestamper: - Move from core to router, leave stub in core so it doesn't break compatibility. This removes a thread in app context and prevents any app context from running NTP; external clients must use the time received from the router. - Increase query interval --- .../net/i2p/router/web/ConfigNetHandler.java | 5 - .../net/i2p/router/web/ConfigNetHelper.java | 9 - core/java/src/net/i2p/time/Timestamper.java | 335 +---------------- core/java/src/net/i2p/time/package.html | 3 +- core/java/src/net/i2p/util/Clock.java | 7 +- history.txt | 19 +- .../java/src/net/i2p/router/RouterClock.java | 10 + .../src/net/i2p/router/RouterVersion.java | 2 +- .../src/net/i2p/router}/time/NtpClient.java | 5 +- .../src/net/i2p/router}/time/NtpMessage.java | 5 +- .../i2p/router/time/RouterTimestamper.java | 340 ++++++++++++++++++ .../java/src/net/i2p/router/time/package.html | 7 + 12 files changed, 402 insertions(+), 345 deletions(-) rename {core/java/src/net/i2p => router/java/src/net/i2p/router}/time/NtpClient.java (98%) rename {core/java/src/net/i2p => router/java/src/net/i2p/router}/time/NtpMessage.java (99%) create mode 100644 router/java/src/net/i2p/router/time/RouterTimestamper.java create mode 100644 router/java/src/net/i2p/router/time/package.html diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java index 5541d0eba..57f8f33c8 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java @@ -61,7 +61,6 @@ public class ConfigNetHandler extends FormHandler { } public void setSave(String moo) { _saveRequested = true; } - public void setEnabletimesync(String moo) { } public void setRecheckReachability(String moo) { _recheckReachabilityRequested = true; } public void setRequireIntroductions(String moo) { _requireIntroductions = true; } public void setDynamicKeys(String moo) { _dynamicKeys = true; } @@ -289,10 +288,6 @@ public class ConfigNetHandler extends FormHandler { removes.add(UDPTransport.PROP_FORCE_INTRODUCERS); } - // Time sync enable, means NOT disabled - // Hmm router sets this at startup, not required here - //changes.put(Timestamper.PROP_DISABLED, "false"); - // Hidden in the GUI //LoadTestManager.setEnableLoadTesting(_context, _enableLoadTesting); } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java index d33d4e44f..0881f51a2 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java @@ -9,7 +9,6 @@ import net.i2p.router.Router; import net.i2p.router.transport.TransportManager; import net.i2p.router.transport.udp.UDPAddress; import net.i2p.router.transport.udp.UDPTransport; -import net.i2p.time.Timestamper; import net.i2p.util.Addresses; public class ConfigNetHelper extends HelperBase { @@ -65,14 +64,6 @@ public class ConfigNetHelper extends HelperBase { return "" + _context.getProperty(UDPTransport.PROP_INTERNAL_PORT, UDPTransport.DEFAULT_INTERNAL_PORT); } - public String getEnableTimeSyncChecked() { - boolean disabled = _context.getBooleanProperty(Timestamper.PROP_DISABLED); - if (disabled) - return ""; - else - return CHECKED; - } - /** @param prop must default to false */ public String getChecked(String prop) { if (_context.getBooleanProperty(prop)) diff --git a/core/java/src/net/i2p/time/Timestamper.java b/core/java/src/net/i2p/time/Timestamper.java index 1e2d1244d..06e5c3052 100644 --- a/core/java/src/net/i2p/time/Timestamper.java +++ b/core/java/src/net/i2p/time/Timestamper.java @@ -1,336 +1,31 @@ package net.i2p.time; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.StringTokenizer; -import java.util.concurrent.CopyOnWriteArrayList; - -import net.i2p.I2PAppContext; -import net.i2p.util.I2PThread; -import net.i2p.util.Log; - /** - * Periodically query a series of NTP servers and update any associated - * listeners. It tries the NTP servers in order, contacting them using - * SNTP (UDP port 123). By default, it does this every 5 minutes, - * forever. + * Dummy. Real thing moved to net.i2p.router.time.RouterTimestamper. + * What remains here is essentially an interface, + * containing only what is needed to keep external apps + * compiled with old libs from breaking, since + * net.i2p.util.Clock returns a Timestamper in getTimestamper() + * + * Deprecated outside of the router. */ public class Timestamper implements Runnable { - private final I2PAppContext _context; - private Log _log; - private final List _servers; - private List _priorityServers; - private final List _listeners; - private int _queryFrequency; - private int _concurringServers; - private int _consecutiveFails; - private volatile boolean _disabled; - private final boolean _daemon; - private boolean _initialized; - private boolean _wellSynced; - private volatile boolean _isRunning; - private Thread _timestamperThread; - private static final int MIN_QUERY_FREQUENCY = 5*60*1000; - private static final int DEFAULT_QUERY_FREQUENCY = 5*60*1000; - private static final String DEFAULT_SERVER_LIST = "0.pool.ntp.org,1.pool.ntp.org,2.pool.ntp.org"; - private static final String DEFAULT_DISABLED = "true"; - /** how many times do we have to query if we are changing the clock? */ - private static final int DEFAULT_CONCURRING_SERVERS = 3; - private static final int MAX_CONSECUTIVE_FAILS = 10; + /** dummy */ + public Timestamper() {} - public static final String PROP_QUERY_FREQUENCY = "time.queryFrequencyMs"; - public static final String PROP_SERVER_LIST = "time.sntpServerList"; - public static final String PROP_DISABLED = "time.disabled"; - public static final String PROP_CONCURRING_SERVERS = "time.concurringServers"; - public static final String PROP_IP_COUNTRY = "i2np.lastCountry"; - - /** if different SNTP servers differ by more than 10s, someone is b0rked */ - private static final int MAX_VARIANCE = 10*1000; - - public Timestamper(I2PAppContext ctx) { - this(ctx, null, true); - } - - public Timestamper(I2PAppContext ctx, UpdateListener lsnr) { - this(ctx, lsnr, true); - } - public Timestamper(I2PAppContext ctx, UpdateListener lsnr, boolean daemon) { - // moved here to prevent problems with synchronized statements. - _servers = new ArrayList(3); - _listeners = new CopyOnWriteArrayList(); - _context = ctx; - _daemon = daemon; - // DO NOT initialize _log here, stack overflow via LogManager init loop - - // Don't bother starting a thread if we are disabled. - // This means we no longer check every 5 minutes to see if we got enabled, - // so the property must be set at startup. - // We still need to be instantiated since the router calls clock().getTimestamper().waitForInitialization() - String disabled = ctx.getProperty(PROP_DISABLED, DEFAULT_DISABLED); - if (Boolean.valueOf(disabled).booleanValue()) { - _initialized = true; - return; - } - if (lsnr != null) - _listeners.add(lsnr); - updateConfig(); - startTimestamper(); - } - - public int getServerCount() { - synchronized (_servers) { - return _servers.size(); - } - } - public String getServer(int index) { - synchronized (_servers) { - return _servers.get(index); - } - } - - public int getQueryFrequencyMs() { return _queryFrequency; } - - public boolean getIsDisabled() { return _disabled; } - - public void addListener(UpdateListener lsnr) { - _listeners.add(lsnr); - } - public void removeListener(UpdateListener lsnr) { - _listeners.remove(lsnr); - } - public int getListenerCount() { - return _listeners.size(); - } - public UpdateListener getListener(int index) { - return _listeners.get(index); - } - - private void startTimestamper() { - _timestamperThread = new I2PThread(this, "Timestamper", _daemon); - _timestamperThread.setPriority(I2PThread.MIN_PRIORITY); - _isRunning = true; - _timestamperThread.start(); - _context.addShutdownTask(new Shutdown()); - } - - public void waitForInitialization() { - try { - synchronized (this) { - if (!_initialized) - wait(); - } - } catch (InterruptedException ie) {} - } + /** dummy */ + public void waitForInitialization() {} /** * Update the time immediately. + * Dummy * @since 0.8.8 */ - public void timestampNow() { - if (_initialized && _isRunning && (!_disabled) && _timestamperThread != null) - _timestamperThread.interrupt(); - } + public void timestampNow() {} - /** @since 0.8.8 */ - private class Shutdown implements Runnable { - public void run() { - _isRunning = false; - if (_timestamperThread != null) - _timestamperThread.interrupt(); - } - } - - public void run() { - try { Thread.sleep(1000); } catch (InterruptedException ie) {} - _log = _context.logManager().getLog(Timestamper.class); - if (_log.shouldLog(Log.INFO)) - _log.info("Starting timestamper"); - boolean lastFailed = false; - try { - while (_isRunning) { - updateConfig(); - if (!_disabled) { - // first the servers for our country, if we know what country we're in... - if (_priorityServers != null) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Querying servers " + _priorityServers); - try { - lastFailed = !queryTime(_priorityServers.toArray(new String[_priorityServers.size()])); - } catch (IllegalArgumentException iae) { - if ( (!lastFailed) && (_log.shouldLog(Log.WARN)) ) - _log.warn("Unable to reach country-specific NTP servers"); - lastFailed = true; - } - } - // ... and then the global list, if that failed - if (_priorityServers == null || lastFailed) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Querying servers " + _servers); - try { - lastFailed = !queryTime(_servers.toArray(new String[_servers.size()])); - } catch (IllegalArgumentException iae) { - if ( (!_initialized) && (_log.shouldLog(Log.ERROR)) ) { - List all = new ArrayList(); - if (_priorityServers != null) - all.addAll(_priorityServers); - all.addAll(_servers); - _log.error("Unable to reach any of the NTP servers " + all + " - network disconnected? Or set time.sntpServerList=myserver1.com,myserver2.com in advanced configuration."); - } - lastFailed = true; - } - } - } - - _initialized = true; - synchronized (this) { notifyAll(); } - long sleepTime; - if (lastFailed) { - if (++_consecutiveFails >= MAX_CONSECUTIVE_FAILS) - sleepTime = 30*60*1000; - else - sleepTime = 30*1000; - } else { - _consecutiveFails = 0; - sleepTime = _context.random().nextInt(_queryFrequency) + _queryFrequency; - if (_wellSynced) - sleepTime *= 3; - } - try { Thread.sleep(sleepTime); } catch (InterruptedException ie) {} - } - } catch (Throwable t) { - _log.log(Log.CRIT, "Timestamper died!", t); - synchronized (this) { notifyAll(); } - } - } - - /** - * True if the time was queried successfully, false if it couldn't be - */ - private boolean queryTime(String serverList[]) throws IllegalArgumentException { - long found[] = new long[_concurringServers]; - long now = -1; - int stratum = -1; - long expectedDelta = 0; - _wellSynced = false; - for (int i = 0; i < _concurringServers; i++) { - if (i > 0) { - // this delays startup when net is disconnected or the timeserver list is bad, don't make it too long - try { Thread.sleep(2*1000); } catch (InterruptedException ie) {} - } - long[] timeAndStratum = NtpClient.currentTimeAndStratum(serverList); - now = timeAndStratum[0]; - stratum = (int) timeAndStratum[1]; - long delta = now - _context.clock().now(); - found[i] = delta; - if (i == 0) { - if (Math.abs(delta) < MAX_VARIANCE) { - if (_log.shouldLog(Log.INFO)) - _log.info("a single SNTP query was within the tolerance (" + delta + "ms)"); - // If less than a half second on the first try, we're in good shape - _wellSynced = Math.abs(delta) < 500; - break; - } else { - // outside the tolerance, lets iterate across the concurring queries - expectedDelta = delta; - } - } else { - if (Math.abs(delta - expectedDelta) > MAX_VARIANCE) { - if (_log.shouldLog(Log.ERROR)) { - StringBuilder err = new StringBuilder(96); - err.append("SNTP client variance exceeded at query ").append(i); - err.append(". expected = "); - err.append(expectedDelta); - err.append(", found = "); - err.append(delta); - err.append(" all deltas: "); - for (int j = 0; j < found.length; j++) - err.append(found[j]).append(' '); - _log.error(err.toString()); - } - return false; - } - } - } - stampTime(now, stratum); - if (_log.shouldLog(Log.DEBUG)) { - StringBuilder buf = new StringBuilder(64); - buf.append("Deltas: "); - for (int i = 0; i < found.length; i++) - buf.append(found[i]).append(' '); - _log.debug(buf.toString()); - } - return true; - } - - /** - * Notify the listeners - * - * @since stratum param added in 0.7.12 - */ - private void stampTime(long now, int stratum) { - long before = _context.clock().now(); - for (UpdateListener lsnr : _listeners) { - lsnr.setNow(now, stratum); - } - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Stamped the time as " + now + " (delta=" + (now-before) + ")"); - } - - /** - * Reload all the config elements from the appContext - * - */ - private void updateConfig() { - String serverList = _context.getProperty(PROP_SERVER_LIST); - if ( (serverList == null) || (serverList.trim().length() <= 0) ) { - serverList = DEFAULT_SERVER_LIST; - String country = _context.getProperty(PROP_IP_COUNTRY); - if (country == null) { - country = Locale.getDefault().getCountry(); - if (country != null) - country = country.toLowerCase(Locale.US); - } - if (country != null && country.length() > 0) { - _priorityServers = new ArrayList(3); - for (int i = 0; i < 3; i++) - _priorityServers.add(i + "." + country + ".pool.ntp.org"); - } else { - _priorityServers = null; - } - } else { - _priorityServers = null; - } - _servers.clear(); - StringTokenizer tok = new StringTokenizer(serverList, ", "); - while (tok.hasMoreTokens()) { - String val = tok.nextToken(); - val = val.trim(); - if (val.length() > 0) - _servers.add(val); - } - - _queryFrequency = Math.max(MIN_QUERY_FREQUENCY, - _context.getProperty(PROP_QUERY_FREQUENCY, DEFAULT_QUERY_FREQUENCY)); - - String disabled = _context.getProperty(PROP_DISABLED, DEFAULT_DISABLED); - _disabled = Boolean.valueOf(disabled).booleanValue(); - - _concurringServers = Math.min(4, Math.max(1, - _context.getProperty(PROP_CONCURRING_SERVERS, DEFAULT_CONCURRING_SERVERS))); - } - -/**** - public static void main(String args[]) { - System.setProperty(PROP_DISABLED, "false"); - System.setProperty(PROP_QUERY_FREQUENCY, "30000"); - I2PAppContext.getGlobalContext(); - for (int i = 0; i < 5*60*1000; i += 61*1000) { - try { Thread.sleep(61*1000); } catch (InterruptedException ie) {} - } - } -****/ + /** dummy */ + public void run() {} /** * Interface to receive update notifications for when we query the time diff --git a/core/java/src/net/i2p/time/package.html b/core/java/src/net/i2p/time/package.html index 18018d978..215fd36da 100644 --- a/core/java/src/net/i2p/time/package.html +++ b/core/java/src/net/i2p/time/package.html @@ -1,7 +1,8 @@

- Provides classes for time synchronisation using NTP. + Provides a stub class for time synchronization. + Full implementation is now in net.i2p.router.time.

diff --git a/core/java/src/net/i2p/util/Clock.java b/core/java/src/net/i2p/util/Clock.java index 767809349..afcf3afaa 100644 --- a/core/java/src/net/i2p/util/Clock.java +++ b/core/java/src/net/i2p/util/Clock.java @@ -19,7 +19,6 @@ import net.i2p.time.Timestamper; */ public class Clock implements Timestamper.UpdateListener { protected final I2PAppContext _context; - private final Timestamper _timestamper; protected long _startedOn; protected boolean _statCreated; protected volatile long _offset; @@ -29,7 +28,6 @@ public class Clock implements Timestamper.UpdateListener { public Clock(I2PAppContext context) { _context = context; _listeners = new CopyOnWriteArraySet(); - _timestamper = new Timestamper(context, this); _startedOn = System.currentTimeMillis(); } @@ -37,7 +35,10 @@ public class Clock implements Timestamper.UpdateListener { return I2PAppContext.getGlobalContext().clock(); } - public Timestamper getTimestamper() { return _timestamper; } + /** + * This is a dummy, see RouterClock and RouterTimestamper for the real thing + */ + public Timestamper getTimestamper() { return new Timestamper(); } /** we fetch it on demand to avoid circular dependencies (logging uses the clock) */ protected Log getLog() { return _context.logManager().getLog(Clock.class); } diff --git a/history.txt b/history.txt index 83d6ca42f..e19fdd241 100644 --- a/history.txt +++ b/history.txt @@ -1,6 +1,21 @@ +2012-05-30 zzz + * Graphs: Reduce log EOF error to warn + * i2psnark: + - Increase max upload (ticket #645) + - Increase per-minute conn limit from 6 to 8 + - Improve rarest-first behavior + - Handle URI encoding, UTF-8, and multiple trackers in magnet links + * Timestamper: + - Move from core to router, leave stub in core + so it doesn't break compatibility. This removes a + thread in app context and prevents any app context from + running NTP; external clients must use the time + received from the router. + - Increase query interval + 2012-05-28 kytv -* i2prouter: Add support so that 'i2prouter install' will work in ArchLinux -* jbigi/jcpuid scripts: Improve support for ArchLinux + * i2prouter: Add support so that 'i2prouter install' will work in ArchLinux + * jbigi/jcpuid scripts: Improve support for ArchLinux 2012-05-25 kytv * German, Italian, Spanish, and Swedish translation updates from Transifex diff --git a/router/java/src/net/i2p/router/RouterClock.java b/router/java/src/net/i2p/router/RouterClock.java index 8f8d20504..0cb6961e4 100644 --- a/router/java/src/net/i2p/router/RouterClock.java +++ b/router/java/src/net/i2p/router/RouterClock.java @@ -4,6 +4,8 @@ import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import net.i2p.data.DataHelper; +import net.i2p.router.time.RouterTimestamper; +import net.i2p.time.Timestamper; import net.i2p.util.Clock; import net.i2p.util.Log; @@ -38,6 +40,7 @@ public class RouterClock extends Clock { /** use system time for this */ private long _lastChanged; private int _lastStratum; + private final Timestamper _timeStamper; /** * If the system clock shifts by this much, @@ -56,8 +59,15 @@ public class RouterClock extends Clock { _lastSlewed = System.currentTimeMillis(); _shiftListeners = new CopyOnWriteArraySet(); _lastShiftNanos = System.nanoTime(); + _timeStamper = new RouterTimestamper(context, this); } + /** + * The RouterTimestamper + */ + @Override + public Timestamper getTimestamper() { return _timeStamper; } + /** * Specify how far away from the "correct" time the computer is - a positive * value means that the system time is slow, while a negative value means the system time is fast. 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 = ""; diff --git a/core/java/src/net/i2p/time/NtpClient.java b/router/java/src/net/i2p/router/time/NtpClient.java similarity index 98% rename from core/java/src/net/i2p/time/NtpClient.java rename to router/java/src/net/i2p/router/time/NtpClient.java index dd873850a..3a1d8ab06 100644 --- a/core/java/src/net/i2p/time/NtpClient.java +++ b/router/java/src/net/i2p/router/time/NtpClient.java @@ -1,4 +1,4 @@ -package net.i2p.time; +package net.i2p.router.time; /* * Copyright (c) 2004, Adam Buckley * All rights reserved. @@ -49,8 +49,9 @@ import java.util.Collections; * * @author Adam Buckley * (minor refactoring by jrandom) + * @since 0.9.1 moved from net.i2p.time */ -public class NtpClient { +class NtpClient { /** difference between the unix epoch and jan 1 1900 (NTP uses that) */ private final static double SECONDS_1900_TO_EPOCH = 2208988800.0; private final static int NTP_PORT = 123; diff --git a/core/java/src/net/i2p/time/NtpMessage.java b/router/java/src/net/i2p/router/time/NtpMessage.java similarity index 99% rename from core/java/src/net/i2p/time/NtpMessage.java rename to router/java/src/net/i2p/router/time/NtpMessage.java index 473fe7232..8a3aa0397 100644 --- a/core/java/src/net/i2p/time/NtpMessage.java +++ b/router/java/src/net/i2p/router/time/NtpMessage.java @@ -1,4 +1,4 @@ -package net.i2p.time; +package net.i2p.router.time; /* * Copyright (c) 2004, Adam Buckley * All rights reserved. @@ -72,8 +72,9 @@ import net.i2p.util.RandomSource; * NTPMessage.java which is copyright (c) 2003 by Juliusz Chroboczek * * @author Adam Buckley + * @since 0.9.1 moved from net.i2p.time */ -public class NtpMessage { +class NtpMessage { /** * This is a two-bit code warning of an impending leap second to be * inserted/deleted in the last minute of the current day. It's values diff --git a/router/java/src/net/i2p/router/time/RouterTimestamper.java b/router/java/src/net/i2p/router/time/RouterTimestamper.java new file mode 100644 index 000000000..83576fde1 --- /dev/null +++ b/router/java/src/net/i2p/router/time/RouterTimestamper.java @@ -0,0 +1,340 @@ +package net.i2p.router.time; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.StringTokenizer; +import java.util.concurrent.CopyOnWriteArrayList; + +import net.i2p.I2PAppContext; +import net.i2p.time.Timestamper; +import net.i2p.util.I2PThread; +import net.i2p.util.Log; + +/** + * Periodically query a series of NTP servers and update any associated + * listeners. It tries the NTP servers in order, contacting them using + * SNTP (UDP port 123). + * + * @since 0.9.1 moved from net.i2p.time + */ +public class RouterTimestamper extends Timestamper { + private final I2PAppContext _context; + private Log _log; + private final List _servers; + private List _priorityServers; + private final List _listeners; + private int _queryFrequency; + private int _concurringServers; + private int _consecutiveFails; + private volatile boolean _disabled; + private final boolean _daemon; + private boolean _initialized; + private boolean _wellSynced; + private volatile boolean _isRunning; + private Thread _timestamperThread; + + private static final int MIN_QUERY_FREQUENCY = 5*60*1000; + private static final int DEFAULT_QUERY_FREQUENCY = 11*60*1000; + private static final String DEFAULT_SERVER_LIST = "0.pool.ntp.org,1.pool.ntp.org,2.pool.ntp.org"; + private static final String DEFAULT_DISABLED = "true"; + /** how many times do we have to query if we are changing the clock? */ + private static final int DEFAULT_CONCURRING_SERVERS = 3; + private static final int MAX_CONSECUTIVE_FAILS = 10; + + public static final String PROP_QUERY_FREQUENCY = "time.queryFrequencyMs"; + public static final String PROP_SERVER_LIST = "time.sntpServerList"; + public static final String PROP_DISABLED = "time.disabled"; + public static final String PROP_CONCURRING_SERVERS = "time.concurringServers"; + public static final String PROP_IP_COUNTRY = "i2np.lastCountry"; + + /** if different SNTP servers differ by more than 10s, someone is b0rked */ + private static final int MAX_VARIANCE = 10*1000; + + public RouterTimestamper(I2PAppContext ctx) { + this(ctx, null, true); + } + + public RouterTimestamper(I2PAppContext ctx, UpdateListener lsnr) { + this(ctx, lsnr, true); + } + public RouterTimestamper(I2PAppContext ctx, UpdateListener lsnr, boolean daemon) { + super(); + // moved here to prevent problems with synchronized statements. + _servers = new ArrayList(3); + _listeners = new CopyOnWriteArrayList(); + _context = ctx; + _daemon = daemon; + // DO NOT initialize _log here, stack overflow via LogManager init loop + + // Don't bother starting a thread if we are disabled. + // This means we no longer check every 5 minutes to see if we got enabled, + // so the property must be set at startup. + // We still need to be instantiated since the router calls clock().getTimestamper().waitForInitialization() + String disabled = ctx.getProperty(PROP_DISABLED, DEFAULT_DISABLED); + if (Boolean.valueOf(disabled).booleanValue()) { + _initialized = true; + return; + } + if (lsnr != null) + _listeners.add(lsnr); + updateConfig(); + startTimestamper(); + } + + public int getServerCount() { + synchronized (_servers) { + return _servers.size(); + } + } + public String getServer(int index) { + synchronized (_servers) { + return _servers.get(index); + } + } + + public int getQueryFrequencyMs() { return _queryFrequency; } + + public boolean getIsDisabled() { return _disabled; } + + public void addListener(UpdateListener lsnr) { + _listeners.add(lsnr); + } + public void removeListener(UpdateListener lsnr) { + _listeners.remove(lsnr); + } + public int getListenerCount() { + return _listeners.size(); + } + public UpdateListener getListener(int index) { + return _listeners.get(index); + } + + private void startTimestamper() { + _timestamperThread = new I2PThread(this, "Timestamper", _daemon); + _timestamperThread.setPriority(I2PThread.MIN_PRIORITY); + _isRunning = true; + _timestamperThread.start(); + _context.addShutdownTask(new Shutdown()); + } + + @Override + public void waitForInitialization() { + try { + synchronized (this) { + if (!_initialized) + wait(); + } + } catch (InterruptedException ie) {} + } + + /** + * Update the time immediately. + * @since 0.8.8 + */ + @Override + public void timestampNow() { + if (_initialized && _isRunning && (!_disabled) && _timestamperThread != null) + _timestamperThread.interrupt(); + } + + /** @since 0.8.8 */ + private class Shutdown implements Runnable { + public void run() { + _isRunning = false; + if (_timestamperThread != null) + _timestamperThread.interrupt(); + } + } + + @Override + public void run() { + try { Thread.sleep(1000); } catch (InterruptedException ie) {} + _log = _context.logManager().getLog(Timestamper.class); + if (_log.shouldLog(Log.INFO)) + _log.info("Starting timestamper"); + boolean lastFailed = false; + try { + while (_isRunning) { + updateConfig(); + if (!_disabled) { + // first the servers for our country, if we know what country we're in... + if (_priorityServers != null) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Querying servers " + _priorityServers); + try { + lastFailed = !queryTime(_priorityServers.toArray(new String[_priorityServers.size()])); + } catch (IllegalArgumentException iae) { + if ( (!lastFailed) && (_log.shouldLog(Log.WARN)) ) + _log.warn("Unable to reach country-specific NTP servers"); + lastFailed = true; + } + } + // ... and then the global list, if that failed + if (_priorityServers == null || lastFailed) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Querying servers " + _servers); + try { + lastFailed = !queryTime(_servers.toArray(new String[_servers.size()])); + } catch (IllegalArgumentException iae) { + if ( (!_initialized) && (_log.shouldLog(Log.ERROR)) ) { + List all = new ArrayList(); + if (_priorityServers != null) + all.addAll(_priorityServers); + all.addAll(_servers); + _log.error("Unable to reach any of the NTP servers " + all + " - network disconnected? Or set time.sntpServerList=myserver1.com,myserver2.com in advanced configuration."); + } + lastFailed = true; + } + } + } + + _initialized = true; + synchronized (this) { notifyAll(); } + long sleepTime; + if (lastFailed) { + if (++_consecutiveFails >= MAX_CONSECUTIVE_FAILS) + sleepTime = 30*60*1000; + else + sleepTime = 30*1000; + } else { + _consecutiveFails = 0; + sleepTime = _context.random().nextInt(_queryFrequency / 2) + _queryFrequency; + if (_wellSynced) + sleepTime *= 3; + } + try { Thread.sleep(sleepTime); } catch (InterruptedException ie) {} + } + } catch (Throwable t) { + _log.log(Log.CRIT, "Timestamper died!", t); + synchronized (this) { notifyAll(); } + } + } + + /** + * True if the time was queried successfully, false if it couldn't be + */ + private boolean queryTime(String serverList[]) throws IllegalArgumentException { + long found[] = new long[_concurringServers]; + long now = -1; + int stratum = -1; + long expectedDelta = 0; + _wellSynced = false; + for (int i = 0; i < _concurringServers; i++) { + if (i > 0) { + // this delays startup when net is disconnected or the timeserver list is bad, don't make it too long + try { Thread.sleep(2*1000); } catch (InterruptedException ie) {} + } + long[] timeAndStratum = NtpClient.currentTimeAndStratum(serverList); + now = timeAndStratum[0]; + stratum = (int) timeAndStratum[1]; + long delta = now - _context.clock().now(); + found[i] = delta; + if (i == 0) { + if (Math.abs(delta) < MAX_VARIANCE) { + if (_log.shouldLog(Log.INFO)) + _log.info("a single SNTP query was within the tolerance (" + delta + "ms)"); + // If less than a half second on the first try, we're in good shape + _wellSynced = Math.abs(delta) < 500; + break; + } else { + // outside the tolerance, lets iterate across the concurring queries + expectedDelta = delta; + } + } else { + if (Math.abs(delta - expectedDelta) > MAX_VARIANCE) { + if (_log.shouldLog(Log.ERROR)) { + StringBuilder err = new StringBuilder(96); + err.append("SNTP client variance exceeded at query ").append(i); + err.append(". expected = "); + err.append(expectedDelta); + err.append(", found = "); + err.append(delta); + err.append(" all deltas: "); + for (int j = 0; j < found.length; j++) + err.append(found[j]).append(' '); + _log.error(err.toString()); + } + return false; + } + } + } + stampTime(now, stratum); + if (_log.shouldLog(Log.DEBUG)) { + StringBuilder buf = new StringBuilder(64); + buf.append("Deltas: "); + for (int i = 0; i < found.length; i++) + buf.append(found[i]).append(' '); + _log.debug(buf.toString()); + } + return true; + } + + /** + * Notify the listeners + * + * @since stratum param added in 0.7.12 + */ + private void stampTime(long now, int stratum) { + long before = _context.clock().now(); + for (UpdateListener lsnr : _listeners) { + lsnr.setNow(now, stratum); + } + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Stamped the time as " + now + " (delta=" + (now-before) + ")"); + } + + /** + * Reload all the config elements from the appContext + * + */ + private void updateConfig() { + String serverList = _context.getProperty(PROP_SERVER_LIST); + if ( (serverList == null) || (serverList.trim().length() <= 0) ) { + serverList = DEFAULT_SERVER_LIST; + String country = _context.getProperty(PROP_IP_COUNTRY); + if (country == null) { + country = Locale.getDefault().getCountry(); + if (country != null) + country = country.toLowerCase(Locale.US); + } + if (country != null && country.length() > 0) { + _priorityServers = new ArrayList(3); + for (int i = 0; i < 3; i++) + _priorityServers.add(i + "." + country + ".pool.ntp.org"); + } else { + _priorityServers = null; + } + } else { + _priorityServers = null; + } + _servers.clear(); + StringTokenizer tok = new StringTokenizer(serverList, ", "); + while (tok.hasMoreTokens()) { + String val = tok.nextToken(); + val = val.trim(); + if (val.length() > 0) + _servers.add(val); + } + + _queryFrequency = Math.max(MIN_QUERY_FREQUENCY, + _context.getProperty(PROP_QUERY_FREQUENCY, DEFAULT_QUERY_FREQUENCY)); + + String disabled = _context.getProperty(PROP_DISABLED, DEFAULT_DISABLED); + _disabled = Boolean.valueOf(disabled).booleanValue(); + + _concurringServers = Math.min(4, Math.max(1, + _context.getProperty(PROP_CONCURRING_SERVERS, DEFAULT_CONCURRING_SERVERS))); + } + +/**** + public static void main(String args[]) { + System.setProperty(PROP_DISABLED, "false"); + System.setProperty(PROP_QUERY_FREQUENCY, "30000"); + I2PAppContext.getGlobalContext(); + for (int i = 0; i < 5*60*1000; i += 61*1000) { + try { Thread.sleep(61*1000); } catch (InterruptedException ie) {} + } + } +****/ +} diff --git a/router/java/src/net/i2p/router/time/package.html b/router/java/src/net/i2p/router/time/package.html new file mode 100644 index 000000000..5ea6ae5e2 --- /dev/null +++ b/router/java/src/net/i2p/router/time/package.html @@ -0,0 +1,7 @@ + + +

+ Provides classes for time synchronization using NTP. +

+ +