forked from I2P_Developers/i2p.i2p
* Naming services:
- Refactor caching - Logging, caching, shutdown cleanup and fixes
This commit is contained in:
@ -71,7 +71,6 @@ import net.metanotion.util.skiplist.SkipList;
|
||||
*/
|
||||
public class BlockfileNamingService extends DummyNamingService {
|
||||
|
||||
private final Log _log;
|
||||
private final BlockFile _bf;
|
||||
private final RandomAccessFile _raf;
|
||||
private final List<String> _lists;
|
||||
@ -104,7 +103,6 @@ public class BlockfileNamingService extends DummyNamingService {
|
||||
*/
|
||||
public BlockfileNamingService(I2PAppContext context) {
|
||||
super(context);
|
||||
_log = context.logManager().getLog(BlockfileNamingService.class);
|
||||
_lists = new ArrayList();
|
||||
BlockFile bf = null;
|
||||
RandomAccessFile raf = null;
|
||||
@ -199,10 +197,11 @@ public class BlockfileNamingService extends DummyNamingService {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
total += count;
|
||||
_log.logAlways(Log.INFO, "Added " + count + " hosts from " + file);
|
||||
_log.logAlways(Log.INFO, "Migrating " + count + " hosts from " + file + " to new hosts database");
|
||||
_lists.add(hostsfile);
|
||||
}
|
||||
_log.error("DB init took " + DataHelper.formatDuration(_context.clock().now() - start));
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("DB init took " + DataHelper.formatDuration(_context.clock().now() - start));
|
||||
if (total <= 0)
|
||||
_log.error("Warning - initialized database with zero entries");
|
||||
return rv;
|
||||
@ -239,8 +238,9 @@ public class BlockfileNamingService extends DummyNamingService {
|
||||
createdOn = Long.parseLong(created);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
_log.error("Found database version " + version + " created " + (new Date(createdOn)).toString() +
|
||||
" containing lists: " + list);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Found database version " + version + " created " + (new Date(createdOn)).toString() +
|
||||
" containing lists: " + list);
|
||||
|
||||
List<String> skiplists = getFilenames(list);
|
||||
if (skiplists.isEmpty())
|
||||
@ -422,6 +422,8 @@ public class BlockfileNamingService extends DummyNamingService {
|
||||
if (changed && checkExisting)
|
||||
return false;
|
||||
addEntry(sl, key, d, props);
|
||||
if (changed)
|
||||
removeCache(hostname);
|
||||
for (NamingServiceListener nsl : _listeners) {
|
||||
if (changed)
|
||||
nsl.entryChanged(this, hostname, d, options);
|
||||
@ -460,6 +462,7 @@ public class BlockfileNamingService extends DummyNamingService {
|
||||
return false;
|
||||
boolean rv = removeEntry(sl, key) != null;
|
||||
if (rv) {
|
||||
removeCache(hostname);
|
||||
for (NamingServiceListener nsl : _listeners) {
|
||||
nsl.entryRemoved(this, key);
|
||||
}
|
||||
|
@ -7,9 +7,7 @@
|
||||
*/
|
||||
package net.i2p.client.naming;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
@ -20,13 +18,19 @@ import net.i2p.data.Destination;
|
||||
* A Dummy naming service that can only handle base64 and b32 destinations.
|
||||
*/
|
||||
class DummyNamingService extends NamingService {
|
||||
private final Map<String, CacheEntry> _cache;
|
||||
|
||||
private static final int BASE32_HASH_LENGTH = 52; // 1 + Hash.HASH_LENGTH * 8 / 5
|
||||
public final static String PROP_B32 = "i2p.naming.hostsTxt.useB32";
|
||||
protected static final int CACHE_MAX_SIZE = 16;
|
||||
protected static final int CACHE_MAX_SIZE = 32;
|
||||
public static final int DEST_SIZE = 516; // Std. Base64 length (no certificate)
|
||||
|
||||
/**
|
||||
* The LRU cache, with no expiration time.
|
||||
* Classes should take care to call removeCache() for any entries that
|
||||
* are invalidated.
|
||||
*/
|
||||
private static final Map<String, Destination> _cache = new LHM(CACHE_MAX_SIZE);
|
||||
|
||||
/**
|
||||
* The naming service should only be constructed and accessed through the
|
||||
* application context. This constructor should only be used by the
|
||||
@ -35,7 +39,6 @@ class DummyNamingService extends NamingService {
|
||||
*/
|
||||
protected DummyNamingService(I2PAppContext context) {
|
||||
super(context);
|
||||
_cache = new HashMap(CACHE_MAX_SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -66,88 +69,48 @@ class DummyNamingService extends NamingService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide basic caching for the service
|
||||
* The service may override the age and/or size limit
|
||||
* Provide basic static caching for all services
|
||||
*/
|
||||
/** Don't know why a dest would ever change but keep this short anyway */
|
||||
protected static final long CACHE_MAX_AGE = 7*60*1000;
|
||||
|
||||
private class CacheEntry {
|
||||
public Destination dest;
|
||||
public long exp;
|
||||
public CacheEntry(Destination d) {
|
||||
dest = d;
|
||||
exp = _context.clock().now() + CACHE_MAX_AGE;
|
||||
}
|
||||
public boolean isExpired() {
|
||||
return exp < _context.clock().now();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up when full.
|
||||
* Don't bother removing old entries unless full.
|
||||
* Caller must synchronize on _cache.
|
||||
*/
|
||||
private void cacheClean() {
|
||||
if (_cache.size() < CACHE_MAX_SIZE)
|
||||
return;
|
||||
boolean full = true;
|
||||
String oldestKey = null;
|
||||
long oldestExp = Long.MAX_VALUE;
|
||||
List<String> deleteList = new ArrayList(CACHE_MAX_SIZE);
|
||||
for (Map.Entry<String, CacheEntry> entry : _cache.entrySet()) {
|
||||
CacheEntry ce = entry.getValue();
|
||||
if (ce.isExpired()) {
|
||||
deleteList.add(entry.getKey());
|
||||
full = false;
|
||||
continue;
|
||||
}
|
||||
if (oldestKey == null || ce.exp < oldestExp) {
|
||||
oldestKey = entry.getKey();
|
||||
oldestExp = ce.exp;
|
||||
}
|
||||
}
|
||||
if (full && oldestKey != null)
|
||||
deleteList.add(oldestKey);
|
||||
for (String s : deleteList) {
|
||||
_cache.remove(s);
|
||||
}
|
||||
}
|
||||
|
||||
protected void putCache(String s, Destination d) {
|
||||
protected static void putCache(String s, Destination d) {
|
||||
if (d == null)
|
||||
return;
|
||||
synchronized (_cache) {
|
||||
_cache.put(s, new CacheEntry(d));
|
||||
cacheClean();
|
||||
_cache.put(s, d);
|
||||
}
|
||||
}
|
||||
|
||||
protected Destination getCache(String s) {
|
||||
/** @return cached dest or null */
|
||||
protected static Destination getCache(String s) {
|
||||
synchronized (_cache) {
|
||||
CacheEntry ce = _cache.get(s);
|
||||
if (ce == null)
|
||||
return null;
|
||||
if (ce.isExpired()) {
|
||||
_cache.remove(s);
|
||||
return null;
|
||||
}
|
||||
return ce.dest;
|
||||
return _cache.get(s);
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 0.8.5 */
|
||||
protected void removeCache(String s) {
|
||||
protected static void removeCache(String s) {
|
||||
synchronized (_cache) {
|
||||
_cache.remove(s);
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 0.8.1 */
|
||||
public void clearCache() {
|
||||
protected static void clearCache() {
|
||||
synchronized (_cache) {
|
||||
_cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static class LHM<K, V> extends LinkedHashMap<K, V> {
|
||||
private final int _max;
|
||||
|
||||
public LHM(int max) {
|
||||
super(max, 0.75f, true);
|
||||
_max = max;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
|
||||
return size() > _max;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,6 @@ public class EepGetNamingService extends DummyNamingService {
|
||||
|
||||
private final static String PROP_EEPGET_LIST = "i2p.naming.eepget.list";
|
||||
private final static String DEFAULT_EEPGET_LIST = "http://i2host.i2p/cgi-bin/i2hostquery?";
|
||||
private final static Log _log = new Log(EepGetNamingService.class);
|
||||
|
||||
/**
|
||||
* The naming service should only be constructed and accessed through the
|
||||
|
@ -48,7 +48,6 @@ public class ExecNamingService extends DummyNamingService {
|
||||
private final static String DEFAULT_EXEC_CMD = "/usr/local/bin/i2presolve";
|
||||
private final static String PROP_SHELL_CMD = "i2p.naming.exec.shell";
|
||||
private final static String DEFAULT_SHELL_CMD = "/bin/bash";
|
||||
private final static Log _log = new Log(ExecNamingService.class);
|
||||
|
||||
/**
|
||||
* The naming service should only be constructed and accessed through the
|
||||
|
@ -90,6 +90,7 @@ public class MetaNamingService extends DummyNamingService {
|
||||
|
||||
@Override
|
||||
public Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions) {
|
||||
// cache check is in super()
|
||||
Destination d = super.lookup(hostname, null, null);
|
||||
if (d != null)
|
||||
return d;
|
||||
@ -136,7 +137,10 @@ public class MetaNamingService extends DummyNamingService {
|
||||
public boolean putIfAbsent(String hostname, Destination d, Properties options) {
|
||||
if (_services.isEmpty())
|
||||
return false;
|
||||
return _services.get(_services.size() - 1).putIfAbsent(hostname, d, options);
|
||||
boolean rv = _services.get(_services.size() - 1).putIfAbsent(hostname, d, options);
|
||||
if (rv)
|
||||
putCache(hostname, d);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -28,7 +28,7 @@ import net.i2p.util.Log;
|
||||
*/
|
||||
public abstract class NamingService {
|
||||
|
||||
private final static Log _log = new Log(NamingService.class);
|
||||
protected final Log _log;
|
||||
protected final I2PAppContext _context;
|
||||
protected final Set<NamingServiceListener> _listeners;
|
||||
protected final Set<NamingServiceUpdater> _updaters;
|
||||
@ -45,6 +45,7 @@ public abstract class NamingService {
|
||||
*/
|
||||
protected NamingService(I2PAppContext context) {
|
||||
_context = context;
|
||||
_log = context.logManager().getLog(getClass());
|
||||
_listeners = new CopyOnWriteArraySet();
|
||||
_updaters = new CopyOnWriteArraySet();
|
||||
}
|
||||
@ -430,7 +431,8 @@ public abstract class NamingService {
|
||||
Constructor con = cls.getConstructor(new Class[] { I2PAppContext.class });
|
||||
instance = (NamingService)con.newInstance(new Object[] { context });
|
||||
} catch (Exception ex) {
|
||||
_log.error("Cannot load naming service " + impl, ex);
|
||||
Log log = context.logManager().getLog(NamingService.class);
|
||||
log.error("Cannot load naming service " + impl, ex);
|
||||
instance = new DummyNamingService(context); // fallback
|
||||
}
|
||||
return instance;
|
||||
|
@ -50,13 +50,13 @@ import net.i2p.util.SecureFileOutputStream;
|
||||
*/
|
||||
public class SingleFileNamingService extends NamingService {
|
||||
|
||||
private final static Log _log = new Log(SingleFileNamingService.class);
|
||||
private final File _file;
|
||||
private final ReentrantReadWriteLock _fileLock;
|
||||
/** cached number of entries */
|
||||
private int _size;
|
||||
/** last write time */
|
||||
private long _lastWrite;
|
||||
private volatile boolean _isClosed;
|
||||
|
||||
public SingleFileNamingService(I2PAppContext context, String filename) {
|
||||
super(context);
|
||||
@ -171,6 +171,8 @@ public class SingleFileNamingService extends NamingService {
|
||||
BufferedReader in = null;
|
||||
BufferedWriter out = null;
|
||||
try {
|
||||
if (_isClosed)
|
||||
return false;
|
||||
File tmp = SecureFile.createTempFile("temp-", ".tmp", _file.getAbsoluteFile().getParentFile());
|
||||
out = new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(tmp), "UTF-8"));
|
||||
if (_file.exists()) {
|
||||
@ -211,10 +213,12 @@ public class SingleFileNamingService extends NamingService {
|
||||
*/
|
||||
@Override
|
||||
public boolean putIfAbsent(String hostname, Destination d, Properties options) {
|
||||
OutputStream out = null;
|
||||
if (!getWriteLock())
|
||||
return false;
|
||||
OutputStream out = null;
|
||||
try {
|
||||
if (_isClosed)
|
||||
return false;
|
||||
// simply check if present, and if not, append
|
||||
try {
|
||||
if (getKey(hostname) != null)
|
||||
@ -250,13 +254,15 @@ public class SingleFileNamingService extends NamingService {
|
||||
*/
|
||||
@Override
|
||||
public boolean remove(String hostname, Properties options) {
|
||||
if (!getWriteLock())
|
||||
return false;
|
||||
if (!_file.exists())
|
||||
return false;
|
||||
BufferedReader in = null;
|
||||
BufferedWriter out = null;
|
||||
if (!getWriteLock())
|
||||
return false;
|
||||
try {
|
||||
if (!_file.exists())
|
||||
return false;
|
||||
if (_isClosed)
|
||||
return false;
|
||||
in = new BufferedReader(new InputStreamReader(new FileInputStream(_file), "UTF-8"), 16*1024);
|
||||
File tmp = SecureFile.createTempFile("temp-", ".tmp", _file.getAbsoluteFile().getParentFile());
|
||||
out = new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(tmp), "UTF-8"));
|
||||
@ -391,6 +397,16 @@ public class SingleFileNamingService extends NamingService {
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (!getWriteLock())
|
||||
return;
|
||||
try {
|
||||
_isClosed = true;
|
||||
} finally {
|
||||
releaseWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean rename(File from, File to) {
|
||||
boolean success = false;
|
||||
boolean isWindows = System.getProperty("os.name").startsWith("Win");
|
||||
|
Reference in New Issue
Block a user