diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java index 9dbf4de23a6552a1c69bfb73533f6850fbf428ca..480950c037d91d28adacbaefe35bd60a5df068eb 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java @@ -147,9 +147,11 @@ public class ConfigClientsHelper extends HelperBase { } desc.append("</table>"); boolean enableStop = !Boolean.valueOf(appProps.getProperty("disableStop")).booleanValue(); + enableStop &= PluginStarter.isPluginRunning(app, _context); + boolean enableStart = !PluginStarter.isPluginRunning(app, _context); renderForm(buf, app, app, false, "true".equals(val), false, desc.toString(), false, false, - updateURL != null, enableStop, true, true); + updateURL != null, enableStop, true, enableStart); } } buf.append("</table>\n"); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java index 7c7c11b474ef99727ccdcf4bbb395f88084c6a28..86dd05d3f13758cb8121e60fd16252d3398741f0 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java @@ -7,18 +7,22 @@ import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; +import java.util.concurrent.ConcurrentHashMap; import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; +import net.i2p.router.Job; import net.i2p.router.RouterContext; import net.i2p.router.startup.ClientAppConfig; import net.i2p.router.startup.LoadClientAppsJob; +import net.i2p.util.ConcurrentHashSet; import net.i2p.util.FileUtil; import net.i2p.util.Log; import net.i2p.util.Translate; @@ -42,6 +46,8 @@ public class PluginStarter implements Runnable { "susimail", "addressbook", "routerconsole" }; 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>>(); public PluginStarter(RouterContext ctx) { _context = ctx; @@ -362,6 +368,15 @@ 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 + 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>()); + for(ClientAppConfig app : apps) { if (action.equals("start") && app.disabled) continue; @@ -407,16 +422,48 @@ public class PluginStarter implements Runnable { // quick check, will throw ClassNotFoundException on error LoadClientAppsJob.testClient(app.className); // run this guy now - LoadClientAppsJob.runClient(app.className, app.clientName, argVal, log); + LoadClientAppsJob.runClient(app.className, app.clientName, argVal, log, pluginThreadGroup); } else { // quick check, will throw ClassNotFoundException on error LoadClientAppsJob.testClient(app.className); // wait before firing it up - ctx.jobQueue().addJob(new LoadClientAppsJob.DelayedRunClient(ctx, app.className, app.clientName, argVal, app.delay)); + Job job = new LoadClientAppsJob.DelayedRunClient(ctx, app.className, app.clientName, argVal, app.delay, pluginThreadGroup); + ctx.jobQueue().addJob(job); + pluginJobs.get(pluginName).add(job); } } } + 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; + } + + log.debug("plugin name = <" + pluginName + ">; threads running? " + isClientThreadRunning(pluginName) + "; webapp runing? " + WebAppStarter.isWebAppRunning(pluginName) + "; jobs running? " + isJobRunning); + return isClientThreadRunning(pluginName) || WebAppStarter.isWebAppRunning(pluginName) || isJobRunning; + } + + /** + * Returns <code>true</code> if one or more client threads are running in a given plugin. + * @param pluginName + * @return + */ + private static boolean isClientThreadRunning(String pluginName) { + ThreadGroup group = pluginThreadGroups.get(pluginName); + if (group == null) + return false; + + Thread[] activeThreads = new Thread[1]; + group.enumerate(activeThreads); + return activeThreads[0] != null; + } + /** * Perhaps there's an easy way to use Thread.setContextClassLoader() * but I don't see how to make it magically get used for everything. diff --git a/apps/routerconsole/java/src/net/i2p/router/web/WebAppStarter.java b/apps/routerconsole/java/src/net/i2p/router/web/WebAppStarter.java index 8a146f5b1ba05b1683f423b322e59893f4afcd85..94b6a4892ad7ac8b4ce9c8f2cc0b0b0b176d901d 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/WebAppStarter.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/WebAppStarter.java @@ -87,6 +87,13 @@ public class WebAppStarter { } catch (IllegalStateException ise) {} } + static boolean isWebAppRunning(String appName) { + Server server = WebAppStarter.getConsoleServer(); + // this will return a new context if one does not exist + HttpContext wac = server.getContext('/' + appName); + return wac.isStarted(); + } + /** see comments in ConfigClientsHandler */ static Server getConsoleServer() { Collection c = Server.getHttpServers(); diff --git a/core/java/src/net/i2p/util/I2PThread.java b/core/java/src/net/i2p/util/I2PThread.java index dcbddd89e86b243296fde2696389327ca5de8882..c21c66f6bebbacd73094b8cdc45118185d261852 100644 --- a/core/java/src/net/i2p/util/I2PThread.java +++ b/core/java/src/net/i2p/util/I2PThread.java @@ -55,6 +55,12 @@ public class I2PThread extends Thread { _createdBy = new Exception("Created by"); } + public I2PThread(ThreadGroup g, Runnable r) { + super(g, r); + if ( (_log == null) || (_log.shouldLog(Log.DEBUG)) ) + _createdBy = new Exception("Created by"); + } + private void log(int level, String msg) { log(level, msg, null); } private void log(int level, String msg, Throwable t) { // we cant assume log is created diff --git a/router/java/src/net/i2p/router/JobQueue.java b/router/java/src/net/i2p/router/JobQueue.java index 06a9004efe3d7894d3f1136f09dc84e54233aadf..facdb8921ce89a9255898decb153e0d44fc42642 100644 --- a/router/java/src/net/i2p/router/JobQueue.java +++ b/router/java/src/net/i2p/router/JobQueue.java @@ -11,6 +11,7 @@ package net.i2p.router; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -192,6 +193,19 @@ public class JobQueue { } } + /** + * Returns <code>true</code> if a given job is waiting or running; + * <code>false</code> if the job is finished or doesn't exist in the queue. + */ + public boolean isJobActive(Job job) { + if (_readyJobs.contains(job) | _timedJobs.contains(job)) + return true; + for (JobQueueRunner runner: _queueRunners.values()) + if (runner.getCurrentJob() == job) + return true; + return false; + } + public void timingUpdated() { synchronized (_jobLock) { _jobLock.notifyAll(); diff --git a/router/java/src/net/i2p/router/startup/LoadClientAppsJob.java b/router/java/src/net/i2p/router/startup/LoadClientAppsJob.java index 1f68c147def78d4976eb03627a32899b57475487..ab21bc8006df24c7e0b56a53d68762a9ca8b1f2b 100644 --- a/router/java/src/net/i2p/router/startup/LoadClientAppsJob.java +++ b/router/java/src/net/i2p/router/startup/LoadClientAppsJob.java @@ -4,6 +4,7 @@ import java.lang.reflect.Method; import java.util.Arrays; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CountDownLatch; import net.i2p.router.JobImpl; import net.i2p.router.RouterContext; @@ -55,18 +56,24 @@ public class LoadClientAppsJob extends JobImpl { private String _clientName; private String _args[]; private Log _log; + private ThreadGroup _threadGroup; public DelayedRunClient(RouterContext enclosingContext, String className, String clientName, String args[], long delay) { + this(enclosingContext, className, clientName, args, delay, null); + } + + public DelayedRunClient(RouterContext enclosingContext, String className, String clientName, String args[], long delay, ThreadGroup threadGroup) { super(enclosingContext); _className = className; _clientName = clientName; _args = args; _log = enclosingContext.logManager().getLog(LoadClientAppsJob.class); + _threadGroup = threadGroup; getTiming().setStartAfter(getContext().clock().now() + delay); } public String getName() { return "Delayed client job"; } public void runJob() { - runClient(_className, _clientName, _args, _log); + runClient(_className, _clientName, _args, _log, _threadGroup); } } @@ -149,9 +156,20 @@ public class LoadClientAppsJob extends JobImpl { * Run client in a new thread. */ public static void runClient(String className, String clientName, String args[], Log log) { + runClient(className, clientName, args, log, null); + } + + /** + * Run client in a new thread. + */ + public static void runClient(String className, String clientName, String args[], Log log, ThreadGroup threadGroup) { if (log.shouldLog(Log.INFO)) log.info("Loading up the client application " + clientName + ": " + className + " " + Arrays.toString(args)); - I2PThread t = new I2PThread(new RunApp(className, clientName, args, log)); + I2PThread t; + if (threadGroup != null) + t = new I2PThread(threadGroup, new RunApp(className, clientName, args, log)); + else + t = new I2PThread(new RunApp(className, clientName, args, log)); if (clientName == null) clientName = className + " client"; t.setName(clientName);