* Plugins: Track pending plugin clients better, don't hold references,

start delayed clients from SimpleTimer2 instead of Job queue (ticket #670)
This commit is contained in:
zzz
2013-04-26 16:41:09 +00:00
parent 1cea18346b
commit 0816cfe273
9 changed files with 89 additions and 42 deletions

View File

@ -20,7 +20,6 @@ import java.util.concurrent.ConcurrentHashMap;
import net.i2p.CoreVersion;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.router.Job;
import net.i2p.router.RouterContext;
import net.i2p.router.RouterVersion;
import net.i2p.router.startup.ClientAppConfig;
@ -31,6 +30,7 @@ import net.i2p.util.ConcurrentHashSet;
import net.i2p.util.FileUtil;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer2;
import net.i2p.util.Translate;
import net.i2p.util.VersionComparator;
@ -55,7 +55,8 @@ public class PluginStarter implements Runnable {
private static final String[] STANDARD_THEMES = { "images", "light", "dark", "classic",
"midnight" };
private static Map<String, ThreadGroup> pluginThreadGroups = new ConcurrentHashMap<String, ThreadGroup>(); // one thread group per plugin (map key=plugin name)
private static Map<String, Collection<Job>> pluginJobs = new ConcurrentHashMap<String, Collection<Job>>();
private static Map<String, Collection<SimpleTimer2.TimedEvent>> _pendingPluginClients =
new ConcurrentHashMap<String, Collection<SimpleTimer2.TimedEvent>>();
private static Map<String, ClassLoader> _clCache = new ConcurrentHashMap();
private static Map<String, Collection<String>> pluginWars = new ConcurrentHashMap<String, Collection<String>>();
@ -592,13 +593,13 @@ public class PluginStarter implements Runnable {
private static void runClientApps(RouterContext ctx, File pluginDir, List<ClientAppConfig> apps, String action) throws Exception {
Log log = ctx.logManager().getLog(PluginStarter.class);
// initialize pluginThreadGroup and pluginJobs
// initialize pluginThreadGroup and _pendingPluginClients
String pluginName = pluginDir.getName();
if (!pluginThreadGroups.containsKey(pluginName))
pluginThreadGroups.put(pluginName, new ThreadGroup(pluginName));
ThreadGroup pluginThreadGroup = pluginThreadGroups.get(pluginName);
if (action.equals("start"))
pluginJobs.put(pluginName, new ConcurrentHashSet<Job>());
_pendingPluginClients.put(pluginName, new ConcurrentHashSet<SimpleTimer2.TimedEvent>());
for(ClientAppConfig app : apps) {
if (action.equals("start") && app.disabled)
@ -675,39 +676,69 @@ public class PluginStarter implements Runnable {
try {
// quick check
LoadClientAppsJob.testClient(app.className, cl);
} catch(ClassNotFoundException ex) {
// Try again 1 or 2 seconds later.
// This should be enough time. Although it is a lousy hack
// it should work for most cases.
// Perhaps it may be even better to delay a percentage
// if > 1, and reduce the delay time.
// Under normal circumstances there will be no delay at all.
if(app.delay > 1) {
Thread.sleep(2000);
} else {
Thread.sleep(1000);
}
}
// quick check, will throw ClassNotFoundException on error
LoadClientAppsJob.testClient(app.className, cl);
// wait before firing it up
Job job = new LoadClientAppsJob.DelayedRunClient(ctx, app.className, app.clientName, argVal, app.delay, pluginThreadGroup, cl);
ctx.jobQueue().addJob(job);
pluginJobs.get(pluginName).add(job);
} catch (ClassNotFoundException ex) {
// Try again 1 or 2 seconds later.
// This should be enough time. Although it is a lousy hack
// it should work for most cases.
// Perhaps it may be even better to delay a percentage
// if > 1, and reduce the delay time.
// Under normal circumstances there will be no delay at all.
try {
if (app.delay > 1) {
Thread.sleep(2000);
} else {
Thread.sleep(1000);
}
} catch (InterruptedException ie) {}
// quick check, will throw ClassNotFoundException on error
LoadClientAppsJob.testClient(app.className, cl);
}
// wait before firing it up
SimpleTimer2.TimedEvent evt = new TrackedDelayedClient(pluginName, ctx.simpleTimer2(), ctx, app.className,
app.clientName, argVal, pluginThreadGroup, cl);
evt.schedule(app.delay);
}
}
}
/**
* Simple override to track whether a plugin's client is delayed and queued
* @since 0.9.6
*/
private static class TrackedDelayedClient extends LoadClientAppsJob.DelayedRunClient {
private final String _pluginName;
public TrackedDelayedClient(String pluginName,
SimpleTimer2 pool, RouterContext enclosingContext, String className, String clientName,
String args[], ThreadGroup threadGroup, ClassLoader cl) {
super(pool, enclosingContext, className, clientName, args, threadGroup, cl);
_pluginName = pluginName;
_pendingPluginClients.get(pluginName).add(this);
}
@Override
public boolean cancel() {
boolean rv = super.cancel();
_pendingPluginClients.get(_pluginName).remove(this);
return rv;
}
@Override
public void timeReached() {
super.timeReached();
_pendingPluginClients.get(_pluginName).remove(this);
}
}
public static boolean isPluginRunning(String pluginName, RouterContext ctx) {
Log log = ctx.logManager().getLog(PluginStarter.class);
boolean isJobRunning = false;
if (pluginJobs.containsKey(pluginName))
for (Job job: pluginJobs.get(pluginName))
if (ctx.jobQueue().isJobActive(job)) {
isJobRunning = true;
break;
}
Collection<SimpleTimer2.TimedEvent> pending = _pendingPluginClients.get(pluginName);
if (pending != null && !pending.isEmpty()) {
// TODO have a pending indication too
isJobRunning = true;
}
boolean isWarRunning = false;
if(pluginWars.containsKey(pluginName)) {
Iterator <String> it = pluginWars.get(pluginName).iterator();

View File

@ -2,6 +2,8 @@
* Console: Show log location on /logs even if not opened yet (ticket #905)
* HTTP proxy: Verify nonce count in digest auth
* i2psnark: Use smaller piece size for small torrents
* Plugins: Track pending plugin clients better, don't hold references,
start delayed clients from SimpleTimer2 instead of Job queue (ticket #670)
2013-04-25 kytv
* Portuguese, Russian, Spanish, and Swedish translation updates from Transifex

View File

@ -12,6 +12,7 @@ package net.i2p.router;
/**
* Defines an executable task
*
* For use by the router only. Not to be used by applications or plugins.
*/
public interface Job {
/**

View File

@ -12,6 +12,8 @@ import java.util.concurrent.atomic.AtomicLong;
/**
* Base implementation of a Job
*
* For use by the router only. Not to be used by applications or plugins.
*/
public abstract class JobImpl implements Job {
private final RouterContext _context;

View File

@ -31,6 +31,7 @@ import net.i2p.util.Log;
* Manage the pending jobs according to whatever algorithm is appropriate, giving
* preference to earlier scheduled jobs.
*
* For use by the router only. Not to be used by applications or plugins.
*/
public class JobQueue {
private final Log _log;

View File

@ -5,6 +5,7 @@ import net.i2p.data.DataHelper;
/**
* Glorified struct to contain basic job stats.
* Public for router console only.
* For use by the router only. Not to be used by applications or plugins.
*/
public class JobStats {
private final String _job;

View File

@ -13,6 +13,7 @@ import net.i2p.util.Clock;
/**
* Define the timing requirements and statistics for a particular job
*
* For use by the router only. Not to be used by applications or plugins.
*/
public class JobTiming implements Clock.ClockUpdateListener {
private long _start;

View File

@ -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 = 16;
public final static long BUILD = 17;
/** for example "-test" */
public final static String EXTRA = "";

View File

@ -14,6 +14,7 @@ import net.i2p.router.RouterContext;
import net.i2p.router.app.RouterApp;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer2;
/**
* Run any client applications specified in clients.config. If any clientApp
@ -51,12 +52,18 @@ public class LoadClientAppsJob extends JobImpl {
runClient(app.className, app.clientName, argVal, getContext(), _log);
} else {
// wait before firing it up
getContext().jobQueue().addJob(new DelayedRunClient(getContext(), app.className, app.clientName, argVal, app.delay));
DelayedRunClient drc = new DelayedRunClient(getContext().simpleTimer2(), getContext(), app.className,
app.clientName, argVal);
drc.schedule(app.delay);
}
}
}
public static class DelayedRunClient extends JobImpl {
/**
* Public for router console only, not for use by others, subject to change
*/
public static class DelayedRunClient extends SimpleTimer2.TimedEvent {
private final RouterContext _ctx;
private final String _className;
private final String _clientName;
private final String _args[];
@ -64,26 +71,27 @@ public class LoadClientAppsJob extends JobImpl {
private final ThreadGroup _threadGroup;
private final ClassLoader _cl;
public DelayedRunClient(RouterContext enclosingContext, String className, String clientName, String args[], long delay) {
this(enclosingContext, className, clientName, args, delay, null, null);
/** caller MUST call schedule() */
public DelayedRunClient(SimpleTimer2 pool, RouterContext enclosingContext, String className,
String clientName, String args[]) {
this(pool, enclosingContext, className, clientName, args, null, null);
}
public DelayedRunClient(RouterContext enclosingContext, String className, String clientName, String args[],
long delay, ThreadGroup threadGroup, ClassLoader cl) {
super(enclosingContext);
/** caller MUST call schedule() */
public DelayedRunClient(SimpleTimer2 pool, RouterContext enclosingContext, String className, String clientName,
String args[], ThreadGroup threadGroup, ClassLoader cl) {
super(pool);
_ctx = enclosingContext;
_className = className;
_clientName = clientName;
_args = args;
_log = enclosingContext.logManager().getLog(LoadClientAppsJob.class);
_threadGroup = threadGroup;
_cl = cl;
getTiming().setStartAfter(getContext().clock().now() + delay);
}
public String getName() { return "Delayed client job"; }
public void runJob() {
runClient(_className, _clientName, _args, getContext(), _log, _threadGroup, _cl);
public void timeReached() {
runClient(_className, _clientName, _args, _ctx, _log, _threadGroup, _cl);
}
}