forked from I2P_Developers/i2p.i2p
* New interface for clients started via clients.config, and a new
manager to track the lifecycle and start/stop clients on demand. Not hooked in to console yet, untested. (ticket #347)
This commit is contained in:
@@ -18,6 +18,7 @@ import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
|
||||
import net.i2p.router.peermanager.PeerManagerFacadeImpl;
|
||||
import net.i2p.router.peermanager.ProfileManagerImpl;
|
||||
import net.i2p.router.peermanager.ProfileOrganizer;
|
||||
import net.i2p.router.startup.RouterAppManager;
|
||||
import net.i2p.router.transport.CommSystemFacadeImpl;
|
||||
import net.i2p.router.transport.FIFOBandwidthLimiter;
|
||||
import net.i2p.router.transport.OutboundMessageRegistry;
|
||||
@@ -58,14 +59,22 @@ public class RouterContext extends I2PAppContext {
|
||||
private MessageValidator _messageValidator;
|
||||
//private MessageStateMonitor _messageStateMonitor;
|
||||
private RouterThrottle _throttle;
|
||||
private RouterAppManager _appManager;
|
||||
private final Set<Runnable> _finalShutdownTasks;
|
||||
// split up big lock on this to avoid deadlocks
|
||||
private volatile boolean _initialized;
|
||||
private final Object _lock1 = new Object(), _lock2 = new Object();
|
||||
|
||||
private static final List<RouterContext> _contexts = new CopyOnWriteArrayList();
|
||||
|
||||
/**
|
||||
* Caller MUST call initAll() after instantiation.
|
||||
*/
|
||||
public RouterContext(Router router) { this(router, null); }
|
||||
|
||||
/**
|
||||
* Caller MUST call initAll() after instantiation.
|
||||
*/
|
||||
public RouterContext(Router router, Properties envProps) {
|
||||
super(filterProps(envProps));
|
||||
_router = router;
|
||||
@@ -141,7 +150,9 @@ public class RouterContext extends I2PAppContext {
|
||||
}
|
||||
|
||||
|
||||
public void initAll() {
|
||||
public synchronized void initAll() {
|
||||
if (_initialized)
|
||||
throw new IllegalStateException();
|
||||
if (getBooleanProperty("i2p.dummyClientFacade"))
|
||||
System.err.println("i2p.dummyClientFacade currently unsupported");
|
||||
_clientManagerFacade = new ClientManagerFacadeImpl(this);
|
||||
@@ -182,6 +193,8 @@ public class RouterContext extends I2PAppContext {
|
||||
_messageValidator = new MessageValidator(this);
|
||||
_throttle = new RouterThrottleImpl(this);
|
||||
//_throttle = new RouterDoSThrottle(this);
|
||||
_appManager = new RouterAppManager(this);
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -495,4 +508,13 @@ public class RouterContext extends I2PAppContext {
|
||||
public InternalClientManager internalClientManager() {
|
||||
return _clientManagerFacade;
|
||||
}
|
||||
|
||||
/**
|
||||
* The RouterAppManager.
|
||||
* @return the manager
|
||||
* @since 0.9.4
|
||||
*/
|
||||
public RouterAppManager clientAppManager() {
|
||||
return _appManager;
|
||||
}
|
||||
}
|
||||
|
24
router/java/src/net/i2p/router/app/RouterApp.java
Normal file
24
router/java/src/net/i2p/router/app/RouterApp.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package net.i2p.router.app;
|
||||
|
||||
import net.i2p.app.ClientApp;
|
||||
|
||||
/**
|
||||
* If a class started via clients.config implements this interface,
|
||||
* it will be used to manage the client, instead of starting with main()
|
||||
*
|
||||
* Clients implementing this interface MUST provide the following constructor:
|
||||
*
|
||||
* public MyClientApp(RouterContext context, ClientAppManager listener, String[] args) {...}
|
||||
*
|
||||
* All parameters are non-null.
|
||||
* This constructor is for instantiation only.
|
||||
* Do not take a long time. Do not block. Never start threads or processes in it.
|
||||
* The ClientAppState of the returned object must be INITIALIZED,
|
||||
* or else throw something.
|
||||
* The startup() method will be called next.
|
||||
*
|
||||
* Never ever hold a static reference to the context or anything derived from it.
|
||||
*
|
||||
* @since 0.9.4
|
||||
*/
|
||||
public interface RouterApp extends ClientApp {}
|
18
router/java/src/net/i2p/router/app/package.html
Normal file
18
router/java/src/net/i2p/router/app/package.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Interface for classes to be started and stopped via clients.config.
|
||||
Classes implementing the RouterApp interface will be controlled with
|
||||
the that interface instead of being started with main().
|
||||
</p>
|
||||
<p>
|
||||
The benefits for clients using this interface:
|
||||
<ul>
|
||||
<li>Get the current context via the constructor
|
||||
<li>Complete life cycle management by the router
|
||||
<li>Avoid the need for static references
|
||||
<li>Ability to find other clients without using static references
|
||||
</ul>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
@@ -1,12 +1,17 @@
|
||||
package net.i2p.router.startup;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.app.ClientApp;
|
||||
import net.i2p.app.ClientAppManager;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.app.RouterApp;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
@@ -24,6 +29,7 @@ public class LoadClientAppsJob extends JobImpl {
|
||||
super(ctx);
|
||||
_log = ctx.logManager().getLog(LoadClientAppsJob.class);
|
||||
}
|
||||
|
||||
public void runJob() {
|
||||
synchronized (LoadClientAppsJob.class) {
|
||||
if (_loaded) return;
|
||||
@@ -42,7 +48,7 @@ public class LoadClientAppsJob extends JobImpl {
|
||||
String argVal[] = parseArgs(app.args);
|
||||
if (app.delay <= 0) {
|
||||
// run this guy now
|
||||
runClient(app.className, app.clientName, argVal, _log);
|
||||
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));
|
||||
@@ -73,9 +79,11 @@ public class LoadClientAppsJob extends JobImpl {
|
||||
_cl = cl;
|
||||
getTiming().setStartAfter(getContext().clock().now() + delay);
|
||||
}
|
||||
|
||||
public String getName() { return "Delayed client job"; }
|
||||
|
||||
public void runJob() {
|
||||
runClient(_className, _clientName, _args, _log, _threadGroup, _cl);
|
||||
runClient(_className, _clientName, _args, getContext(), _log, _threadGroup, _cl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,8 +187,8 @@ public class LoadClientAppsJob extends JobImpl {
|
||||
* @param clientName can be null
|
||||
* @param args can be null
|
||||
*/
|
||||
public static void runClient(String className, String clientName, String args[], Log log) {
|
||||
runClient(className, clientName, args, log, null, null);
|
||||
public static void runClient(String className, String clientName, String args[], RouterContext ctx, Log log) {
|
||||
runClient(className, clientName, args, ctx, log, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,15 +200,15 @@ public class LoadClientAppsJob extends JobImpl {
|
||||
* @param cl can be null
|
||||
* @since 0.7.13
|
||||
*/
|
||||
public static void runClient(String className, String clientName, String args[], Log log,
|
||||
public static void runClient(String className, String clientName, String args[], RouterContext ctx, Log log,
|
||||
ThreadGroup threadGroup, ClassLoader cl) {
|
||||
if (log.shouldLog(Log.INFO))
|
||||
log.info("Loading up the client application " + clientName + ": " + className + " " + Arrays.toString(args));
|
||||
I2PThread t;
|
||||
if (threadGroup != null)
|
||||
t = new I2PThread(threadGroup, new RunApp(className, clientName, args, log, cl));
|
||||
t = new I2PThread(threadGroup, new RunApp(className, clientName, args, ctx, log, cl));
|
||||
else
|
||||
t = new I2PThread(new RunApp(className, clientName, args, log, cl));
|
||||
t = new I2PThread(new RunApp(className, clientName, args, ctx, log, cl));
|
||||
if (clientName == null)
|
||||
clientName = className + " client";
|
||||
t.setName(clientName);
|
||||
@@ -214,16 +222,18 @@ public class LoadClientAppsJob extends JobImpl {
|
||||
private final String _className;
|
||||
private final String _appName;
|
||||
private final String _args[];
|
||||
private final RouterContext _ctx;
|
||||
private final Log _log;
|
||||
private final ClassLoader _cl;
|
||||
|
||||
public RunApp(String className, String appName, String args[], Log log, ClassLoader cl) {
|
||||
public RunApp(String className, String appName, String args[], RouterContext ctx, Log log, ClassLoader cl) {
|
||||
_className = className;
|
||||
_appName = appName;
|
||||
if (args == null)
|
||||
_args = new String[0];
|
||||
else
|
||||
_args = args;
|
||||
_ctx = ctx;
|
||||
_log = log;
|
||||
if (cl == null)
|
||||
_cl = ClassLoader.getSystemClassLoader();
|
||||
@@ -234,14 +244,53 @@ public class LoadClientAppsJob extends JobImpl {
|
||||
public void run() {
|
||||
try {
|
||||
Class cls = Class.forName(_className, true, _cl);
|
||||
Method method = cls.getMethod("main", new Class[] { String[].class });
|
||||
method.invoke(cls, new Object[] { _args });
|
||||
if (isRouterApp(cls)) {
|
||||
Constructor con = cls.getConstructor(RouterContext.class, ClientAppManager.class, String[].class);
|
||||
RouterAppManager mgr = _ctx.clientAppManager();
|
||||
Object[] conArgs = new Object[] {_ctx, _ctx.clientAppManager(), _args};
|
||||
RouterApp app = (RouterApp) con.newInstance(conArgs);
|
||||
mgr.addAndStart(app);
|
||||
} else if (isClientApp(cls)) {
|
||||
Constructor con = cls.getConstructor(I2PAppContext.class, ClientAppManager.class, String[].class);
|
||||
RouterAppManager mgr = _ctx.clientAppManager();
|
||||
Object[] conArgs = new Object[] {_ctx, _ctx.clientAppManager(), _args};
|
||||
ClientApp app = (ClientApp) con.newInstance(conArgs);
|
||||
mgr.addAndStart(app);
|
||||
} else {
|
||||
Method method = cls.getMethod("main", new Class[] { String[].class });
|
||||
method.invoke(cls, new Object[] { _args });
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
_log.log(Log.CRIT, "Error starting up the client class " + _className, t);
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Done running client application " + _appName);
|
||||
}
|
||||
|
||||
private static boolean isRouterApp(Class cls) {
|
||||
return isInterface(cls, RouterApp.class);
|
||||
}
|
||||
|
||||
private static boolean isClientApp(Class cls) {
|
||||
return isInterface(cls, ClientApp.class);
|
||||
}
|
||||
|
||||
private static boolean isInterface(Class cls, Class intfc) {
|
||||
try {
|
||||
Class[] intfcs = cls.getInterfaces();
|
||||
for (int i = 0; i < intfcs.length; i++) {
|
||||
if (intfcs[i] == intfc)
|
||||
return true;
|
||||
}
|
||||
} catch (Throwable t) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public String getName() { return "Load up any client applications"; }
|
||||
|
131
router/java/src/net/i2p/router/startup/RouterAppManager.java
Normal file
131
router/java/src/net/i2p/router/startup/RouterAppManager.java
Normal file
@@ -0,0 +1,131 @@
|
||||
package net.i2p.router.startup;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.i2p.app.*;
|
||||
import static net.i2p.app.ClientAppState.*;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.ConcurrentHashSet;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Notify the router of events, and provide methods for
|
||||
* client apps to find each other.
|
||||
*
|
||||
* @since 0.9.4
|
||||
*/
|
||||
public class RouterAppManager implements ClientAppManager {
|
||||
|
||||
private final RouterContext _context;
|
||||
private final Log _log;
|
||||
private final Set<ClientApp> _clients;
|
||||
private final ConcurrentHashMap<String, ClientApp> _registered;
|
||||
|
||||
public RouterAppManager(RouterContext ctx) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(RouterAppManager.class);
|
||||
_clients = new ConcurrentHashSet(16);
|
||||
_registered = new ConcurrentHashMap(8);
|
||||
}
|
||||
|
||||
public void addAndStart(ClientApp app) {
|
||||
_clients.add(app);
|
||||
try {
|
||||
app.startup();
|
||||
} catch (Throwable t) {
|
||||
_clients.remove(app);
|
||||
_log.error("Client " + app + " failed to start");
|
||||
}
|
||||
}
|
||||
|
||||
// ClientAppManager methods
|
||||
|
||||
/**
|
||||
* Must be called on all state transitions except
|
||||
* from UNINITIALIZED to INITIALIZED.
|
||||
*
|
||||
* @param app non-null
|
||||
* @param state non-null
|
||||
* @param message may be null
|
||||
* @param e may be null
|
||||
*/
|
||||
public void notify(ClientApp app, ClientAppState state, String message, Exception e) {
|
||||
switch(state) {
|
||||
case UNINITIALIZED:
|
||||
case INITIALIZED:
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Client " + app.getDisplayName() + " called notify for" + state);
|
||||
break;
|
||||
|
||||
case STARTING:
|
||||
case RUNNING:
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Client " + app.getDisplayName() + " called notify for" + state);
|
||||
break;
|
||||
|
||||
case FORKED:
|
||||
case STOPPING:
|
||||
case STOPPED:
|
||||
_clients.remove(app);
|
||||
_registered.remove(app.getName(), app);
|
||||
if (message == null)
|
||||
message = "";
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Client " + app.getDisplayName() + " called notify for" + state +
|
||||
' ' + message, e);
|
||||
break;
|
||||
|
||||
case CRASHED:
|
||||
case START_FAILED:
|
||||
_clients.remove(app);
|
||||
_registered.remove(app.getName(), app);
|
||||
if (message == null)
|
||||
message = "";
|
||||
_log.log(Log.CRIT, "Client " + app.getDisplayName() + ' ' + state +
|
||||
' ' + message, e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register with the manager under the given name,
|
||||
* so that other clients may find it.
|
||||
* Only required for apps used by other apps.
|
||||
*
|
||||
* @param app non-null
|
||||
* @param name non-null
|
||||
* @return true if successful, false if duplicate name
|
||||
*/
|
||||
public boolean register(ClientApp app) {
|
||||
if (!_clients.contains(app))
|
||||
return false;
|
||||
// TODO if old app in there is not running and != this app, allow replacement
|
||||
return _registered.putIfAbsent(app.getName(), app) == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister with the manager. Name must be the same as that from register().
|
||||
* Only required for apps used by other apps.
|
||||
*
|
||||
* @param app non-null
|
||||
* @param name non-null
|
||||
*/
|
||||
public void unregister(ClientApp app) {
|
||||
_registered.remove(app.getName(), app);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a registered app.
|
||||
* Only used for apps finding other apps.
|
||||
* Do not hold a static reference.
|
||||
* If you only need to find a port, use the PortMapper instead.
|
||||
*
|
||||
* @param app non-null
|
||||
* @param name non-null
|
||||
* @return client app or null
|
||||
*/
|
||||
public ClientApp getRegisteredApp(String name) {
|
||||
return _registered.get(name);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user