* 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.CoreVersion;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
import net.i2p.router.Job;
import net.i2p.router.RouterContext; import net.i2p.router.RouterContext;
import net.i2p.router.RouterVersion; import net.i2p.router.RouterVersion;
import net.i2p.router.startup.ClientAppConfig; import net.i2p.router.startup.ClientAppConfig;
@@ -31,6 +30,7 @@ import net.i2p.util.ConcurrentHashSet;
import net.i2p.util.FileUtil; import net.i2p.util.FileUtil;
import net.i2p.util.I2PAppThread; import net.i2p.util.I2PAppThread;
import net.i2p.util.Log; import net.i2p.util.Log;
import net.i2p.util.SimpleTimer2;
import net.i2p.util.Translate; import net.i2p.util.Translate;
import net.i2p.util.VersionComparator; import net.i2p.util.VersionComparator;
@@ -55,7 +55,8 @@ public class PluginStarter implements Runnable {
private static final String[] STANDARD_THEMES = { "images", "light", "dark", "classic", private static final String[] STANDARD_THEMES = { "images", "light", "dark", "classic",
"midnight" }; "midnight" };
private static Map<String, ThreadGroup> pluginThreadGroups = new ConcurrentHashMap<String, ThreadGroup>(); // one thread group per plugin (map key=plugin name) 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, ClassLoader> _clCache = new ConcurrentHashMap();
private static Map<String, Collection<String>> pluginWars = new ConcurrentHashMap<String, Collection<String>>(); 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 { private static void runClientApps(RouterContext ctx, File pluginDir, List<ClientAppConfig> apps, String action) throws Exception {
Log log = ctx.logManager().getLog(PluginStarter.class); Log log = ctx.logManager().getLog(PluginStarter.class);
// initialize pluginThreadGroup and pluginJobs // initialize pluginThreadGroup and _pendingPluginClients
String pluginName = pluginDir.getName(); String pluginName = pluginDir.getName();
if (!pluginThreadGroups.containsKey(pluginName)) if (!pluginThreadGroups.containsKey(pluginName))
pluginThreadGroups.put(pluginName, new ThreadGroup(pluginName)); pluginThreadGroups.put(pluginName, new ThreadGroup(pluginName));
ThreadGroup pluginThreadGroup = pluginThreadGroups.get(pluginName); ThreadGroup pluginThreadGroup = pluginThreadGroups.get(pluginName);
if (action.equals("start")) if (action.equals("start"))
pluginJobs.put(pluginName, new ConcurrentHashSet<Job>()); _pendingPluginClients.put(pluginName, new ConcurrentHashSet<SimpleTimer2.TimedEvent>());
for(ClientAppConfig app : apps) { for(ClientAppConfig app : apps) {
if (action.equals("start") && app.disabled) if (action.equals("start") && app.disabled)
@@ -675,39 +676,69 @@ public class PluginStarter implements Runnable {
try { try {
// quick check // quick check
LoadClientAppsJob.testClient(app.className, cl); LoadClientAppsJob.testClient(app.className, cl);
} catch(ClassNotFoundException ex) { } catch (ClassNotFoundException ex) {
// Try again 1 or 2 seconds later. // Try again 1 or 2 seconds later.
// This should be enough time. Although it is a lousy hack // This should be enough time. Although it is a lousy hack
// it should work for most cases. // it should work for most cases.
// Perhaps it may be even better to delay a percentage // Perhaps it may be even better to delay a percentage
// if > 1, and reduce the delay time. // if > 1, and reduce the delay time.
// Under normal circumstances there will be no delay at all. // Under normal circumstances there will be no delay at all.
if(app.delay > 1) { try {
Thread.sleep(2000); if (app.delay > 1) {
} else { Thread.sleep(2000);
Thread.sleep(1000); } else {
} Thread.sleep(1000);
} }
// quick check, will throw ClassNotFoundException on error } catch (InterruptedException ie) {}
LoadClientAppsJob.testClient(app.className, cl); // quick check, will throw ClassNotFoundException on error
// wait before firing it up LoadClientAppsJob.testClient(app.className, cl);
Job job = new LoadClientAppsJob.DelayedRunClient(ctx, app.className, app.clientName, argVal, app.delay, pluginThreadGroup, cl); }
ctx.jobQueue().addJob(job); // wait before firing it up
pluginJobs.get(pluginName).add(job); 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) { public static boolean isPluginRunning(String pluginName, RouterContext ctx) {
Log log = ctx.logManager().getLog(PluginStarter.class); Log log = ctx.logManager().getLog(PluginStarter.class);
boolean isJobRunning = false; boolean isJobRunning = false;
if (pluginJobs.containsKey(pluginName)) Collection<SimpleTimer2.TimedEvent> pending = _pendingPluginClients.get(pluginName);
for (Job job: pluginJobs.get(pluginName)) if (pending != null && !pending.isEmpty()) {
if (ctx.jobQueue().isJobActive(job)) { // TODO have a pending indication too
isJobRunning = true; isJobRunning = true;
break; }
}
boolean isWarRunning = false; boolean isWarRunning = false;
if(pluginWars.containsKey(pluginName)) { if(pluginWars.containsKey(pluginName)) {
Iterator <String> it = pluginWars.get(pluginName).iterator(); 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) * Console: Show log location on /logs even if not opened yet (ticket #905)
* HTTP proxy: Verify nonce count in digest auth * HTTP proxy: Verify nonce count in digest auth
* i2psnark: Use smaller piece size for small torrents * 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 2013-04-25 kytv
* Portuguese, Russian, Spanish, and Swedish translation updates from Transifex * Portuguese, Russian, Spanish, and Swedish translation updates from Transifex

View File

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

View File

@@ -12,6 +12,8 @@ import java.util.concurrent.atomic.AtomicLong;
/** /**
* Base implementation of a Job * 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 { public abstract class JobImpl implements Job {
private final RouterContext _context; 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 * Manage the pending jobs according to whatever algorithm is appropriate, giving
* preference to earlier scheduled jobs. * preference to earlier scheduled jobs.
* *
* For use by the router only. Not to be used by applications or plugins.
*/ */
public class JobQueue { public class JobQueue {
private final Log _log; private final Log _log;

View File

@@ -5,6 +5,7 @@ import net.i2p.data.DataHelper;
/** /**
* Glorified struct to contain basic job stats. * Glorified struct to contain basic job stats.
* Public for router console only. * Public for router console only.
* For use by the router only. Not to be used by applications or plugins.
*/ */
public class JobStats { public class JobStats {
private final String _job; 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 * 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 { public class JobTiming implements Clock.ClockUpdateListener {
private long _start; private long _start;

View File

@@ -18,7 +18,7 @@ public class RouterVersion {
/** deprecated */ /** deprecated */
public final static String ID = "Monotone"; public final static String ID = "Monotone";
public final static String VERSION = CoreVersion.VERSION; public final static String VERSION = CoreVersion.VERSION;
public final static long BUILD = 16; public final static long BUILD = 17;
/** for example "-test" */ /** for example "-test" */
public final static String EXTRA = ""; 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.router.app.RouterApp;
import net.i2p.util.I2PThread; import net.i2p.util.I2PThread;
import net.i2p.util.Log; import net.i2p.util.Log;
import net.i2p.util.SimpleTimer2;
/** /**
* Run any client applications specified in clients.config. If any clientApp * 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); runClient(app.className, app.clientName, argVal, getContext(), _log);
} else { } else {
// wait before firing it up // 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 _className;
private final String _clientName; private final String _clientName;
private final String _args[]; private final String _args[];
@@ -64,26 +71,27 @@ public class LoadClientAppsJob extends JobImpl {
private final ThreadGroup _threadGroup; private final ThreadGroup _threadGroup;
private final ClassLoader _cl; private final ClassLoader _cl;
public DelayedRunClient(RouterContext enclosingContext, String className, String clientName, String args[], long delay) { /** caller MUST call schedule() */
this(enclosingContext, className, clientName, args, delay, null, null); 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[], /** caller MUST call schedule() */
long delay, ThreadGroup threadGroup, ClassLoader cl) { public DelayedRunClient(SimpleTimer2 pool, RouterContext enclosingContext, String className, String clientName,
super(enclosingContext); String args[], ThreadGroup threadGroup, ClassLoader cl) {
super(pool);
_ctx = enclosingContext;
_className = className; _className = className;
_clientName = clientName; _clientName = clientName;
_args = args; _args = args;
_log = enclosingContext.logManager().getLog(LoadClientAppsJob.class); _log = enclosingContext.logManager().getLog(LoadClientAppsJob.class);
_threadGroup = threadGroup; _threadGroup = threadGroup;
_cl = cl; _cl = cl;
getTiming().setStartAfter(getContext().clock().now() + delay);
} }
public String getName() { return "Delayed client job"; } public void timeReached() {
runClient(_className, _clientName, _args, _ctx, _log, _threadGroup, _cl);
public void runJob() {
runClient(_className, _clientName, _args, getContext(), _log, _threadGroup, _cl);
} }
} }