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);