Bureaucracy review updates and hello world test

* Fleshed out client-side Agent class, made abstract
* Delegate the creation of the bureau director to subclasses of BureauClient
* Moved BureauDirector's BureauReceiver and Subscriber implementations to 
  anonymous classes that delegate to protected methods.
* Use SafeSubscriber
* Added new service method for notiyfing the server when the creation of an
  agent fails so that the registry is not perpetually waiting
* Made BureauCredentials more conformant
* Made BureauRegistry respond properly to client logging off
* Got rid of superfluous uses of safeToString
* Changed Launcher to CommandGenerator and now use ProcessBuilder and 
  copyMergedOutput
* Changed bureau process parameters to server name and port instead of URL


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@5030 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Jamie Doornbos
2008-04-29 18:34:51 +00:00
parent 938d7f5994
commit f41233c5ac
12 changed files with 558 additions and 128 deletions
@@ -30,8 +30,11 @@ import com.threerings.bureau.data.AgentObject;
import com.threerings.bureau.data.BureauCodes;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.dobj.RootDObjectManager;
import com.threerings.presents.dobj.ObjectDeathListener;
import com.threerings.presents.dobj.ObjectDestroyedEvent;
import com.threerings.presents.server.InvocationManager;
import static com.samskivert.util.StringUtil.safeToString;
import com.samskivert.util.StringUtil;
import com.samskivert.util.ProcessLogger;
import com.threerings.bureau.Log;
@@ -42,31 +45,37 @@ import com.threerings.bureau.Log;
public class BureauRegistry
{
/**
* Defines a way to launch a bureau. Launchers are set by the server on startup and are
* invoked whenever an agent is requested whose bureauType matches the registered type.
* @see registerBureau
* Defines the commands that are responsible for invoking a bureau. Instances are associated to
* bureau types by the server on startup. The instances are used whenever the registry needs to
* launch a bureau for an agent with the assocated bureau type.
* @see setCommandGenerator
*/
public static interface Launcher
public static interface CommandGenerator
{
/**
* Launches a new bureau using the given server connect-back url and other information.
* Called by the registry when it decides a new bureau is needed.
* @param serverUrl the url the bureau should use to connect back to the server
* @param serverNameAndPort the name and port the bureau should use to connect back to the
* server, e.g. server.com:47624
* @param bureauId the id of the bureau being launched
* @param token the token string to use for the credentials when logging in
* @return the builder, ready to launch
*/
Process launch (String serverUrl, String bureauId, String token);
String[] createCommand (
String serverNameAndPort,
String bureauId,
String token);
}
/**
* Creates a new registry, prepared to provide bureau services.
*/
public BureauRegistry (
String serverUrl,
String serverNameAndPort,
InvocationManager invmgr,
RootDObjectManager omgr)
{
_serverUrl = serverUrl;
_serverNameAndPort = serverNameAndPort;
_invmgr = invmgr;
_omgr = omgr;
@@ -74,9 +83,12 @@ public class BureauRegistry
public void bureauInitialized (ClientObject client, String bureauId) {
BureauRegistry.this.bureauInitialized(client, bureauId);
}
public void agentCreated (ClientObject client, int agentId) {
public void agentCreated (ClientObject client, int agentId) {
BureauRegistry.this.agentCreated(client, agentId);
}
public void agentCreationFailed (ClientObject client, int agentId) {
BureauRegistry.this.agentCreationFailed(client, agentId);
}
public void agentDestroyed (ClientObject client, int agentId) {
BureauRegistry.this.agentDestroyed(client, agentId);
}
@@ -88,20 +100,21 @@ public class BureauRegistry
}
/**
* Registers a launcher of a given type. When an agent is started and no bureaus are currently
* running, the <code>bureauType</code> is used to determine the <code>Launcher</code> to call.
* Registers a command generator for a given type. When an agent is started and no bureaus are
* running, the <code>bureauType</code> is used to determine the <code>CommandGenerator</code>
* instance to call.
* @param bureauType the type of bureau that will be launched
* @param launcher the launcher to be used when the <code>bureauType</code> is requested
* @param cmdGenerator the generator to be used for bureaus of <code>bureauType</code>
*/
public void setLauncher (String bureauType, Launcher launcher)
public void setCommandGenerator (String bureauType, CommandGenerator cmdGenerator)
{
if (_launchers.get(bureauType) != null) {
Log.warning("Launcher for type already exists [type=" +
if (_generators.get(bureauType) != null) {
Log.warning("Generator for type already exists [type=" +
bureauType + "]");
return;
}
_launchers.put(bureauType, launcher);
_generators.put(bureauType, cmdGenerator);
}
/**
@@ -113,7 +126,7 @@ public class BureauRegistry
if (bureau != null && bureau.ready()) {
Log.info("Bureau ready, sending createAgent " +
safeToString(agent));
StringUtil.toString(agent));
BureauSender.createAgent(bureau.clientObj, agent.getOid());
// !TODO: is this the right place to register the object?
@@ -127,29 +140,38 @@ public class BureauRegistry
if (bureau == null) {
Launcher launcher = _launchers.get(agent.bureauType);
if (launcher == null) {
Log.warning("Launcher not found for agent's " +
"bureau type " + safeToString(agent));
CommandGenerator generator = _generators.get(agent.bureauType);
if (generator == null) {
Log.warning("CommandGenerator not found for agent's " +
"bureau type " + StringUtil.toString(agent));
return;
}
Log.info("Creating new bureau " +
safeToString(agent) + " " +
safeToString(launcher));
StringUtil.toString(agent) + " " +
StringUtil.toString(generator));
bureau = new Bureau();
bureau.bureauId = agent.bureauId;
try {
bureau.process = launcher.launch(_serverUrl, agent.bureauId, "");
// kick off the bureau's process
ProcessBuilder builder = new ProcessBuilder(
generator.createCommand(
_serverNameAndPort, agent.bureauId, ""));
builder.redirectErrorStream(true);
bureau.process = builder.start();
// log the output of the process and prefix with bureau id
ProcessLogger.copyMergedOutput(
Log.log, bureau.bureauId, bureau.process);
}
catch (Exception e) {
Log.warning("Could not launch process for bureau " +
safeToString(agent));
StringUtil.toString(agent));
Log.logStackTrace(e);
// !TODO: how to hook into caller and keep the other clients from hanging
return;
}
@@ -173,7 +195,7 @@ public class BureauRegistry
return;
}
Log.warning("Destroying agent " + safeToString(agent));
Log.warning("Destroying agent " + StringUtil.toString(agent));
// transition the agent to a new state and perform the effect of the transition
switch (found.state) {
@@ -197,7 +219,7 @@ public class BureauRegistry
case Bureau.STILL_BORN:
Log.warning("Acknowledging a request to destory an agent, but agent " +
"is in state " + found.state + ", ignoring request " +
safeToString(found.agent));
StringUtil.toString(found.agent));
break;
}
@@ -210,16 +232,22 @@ public class BureauRegistry
*/
protected synchronized void bureauInitialized (ClientObject client, String bureauId)
{
Bureau bureau = _bureaus.get(bureauId);
final Bureau bureau = _bureaus.get(bureauId);
if (bureau == null) {
Log.warning("Acknowledging initialization of non-existent bureau " +
safeToString(bureauId));
StringUtil.toString(bureauId));
return;
}
bureau.clientObj = client;
Log.info("Bureau created " + safeToString(bureau) +
bureau.clientObj.addListener(new ObjectDeathListener() {
public void objectDestroyed (ObjectDestroyedEvent e) {
BureauRegistry.this.clientDestroyed(bureau);
}
});
Log.info("Bureau created " + StringUtil.toString(bureau) +
", launching pending agents");
// find all pending agents
@@ -235,7 +263,7 @@ public class BureauRegistry
// create them
for (AgentObject agent : pending) {
Log.info("Creating agent " + safeToString(agent));
Log.info("Creating agent " + StringUtil.toString(agent));
BureauSender.createAgent(bureau.clientObj, agent.getOid());
bureau.agentStates.put(agent, Bureau.STARTED);
}
@@ -268,7 +296,38 @@ public class BureauRegistry
case Bureau.DESTROYED:
Log.warning("Received acknowledgement of the creation of an " +
"agent in state " + found.state + ", ignoring request " +
safeToString(found.agent));
StringUtil.toString(found.agent));
break;
}
found.bureau.summarize();
}
/**
* Callback for when the bureau client acknowledges the creation of an agent.
*/
protected synchronized void agentCreationFailed (ClientObject client, int agentId)
{
FoundAgent found = resolve(client, agentId, "agentCreationFailed");
if (found == null) {
return;
}
switch (found.state) {
case Bureau.STARTED:
found.bureau.agentStates.remove(found.agent);
break;
case Bureau.STILL_BORN:
found.bureau.agentStates.remove(found.agent);
break;
case Bureau.PENDING:
case Bureau.RUNNING:
case Bureau.DESTROYED:
Log.warning("Received acknowledgement of creation failure for " +
"agent in state " + found.state + ", ignoring request " +
StringUtil.toString(found.agent));
break;
}
@@ -296,13 +355,22 @@ public class BureauRegistry
case Bureau.STILL_BORN:
Log.warning("Acknowledging agent destruction, but state is " +
found.state + ", ignoring request " +
safeToString(found.agent));
StringUtil.toString(found.agent));
break;
}
found.bureau.summarize();
}
// TODO: schedule a shutdown event for the bureau if this is the last agent
/**
* Callback for when a client is destroyed.
*/
protected synchronized void clientDestroyed (Bureau bureau)
{
// clean up any agents attached to this bureau
for (AgentObject agent : bureau.agentStates.keySet()) {
_omgr.destroyObject(agent.getOid());
}
}
/**
@@ -319,7 +387,7 @@ public class BureauRegistry
if (!(dobj instanceof AgentObject)) {
Log.warning("Object not an agent in " + resolver +
" " + safeToString(dobj));
" " + StringUtil.toString(dobj));
return null;
}
@@ -327,27 +395,27 @@ public class BureauRegistry
Bureau bureau = _bureaus.get(agent.bureauId);
if (bureau == null) {
Log.warning("Bureau not found for agent in " + resolver +
" " + safeToString(agent));
" " + StringUtil.toString(agent));
return null;
}
if (!bureau.agentStates.containsKey(agent)) {
Log.warning("Bureau does not have agent in " + resolver +
" " + safeToString(agent));
" " + StringUtil.toString(agent));
return null;
}
if (bureau.clientObj == null) {
Log.warning("Bureau not yet connected in " + resolver +
" " + safeToString(agent));
" " + StringUtil.toString(agent));
return null;
}
if (client != null && bureau.clientObj != client) {
Log.warning("Masquerading request in " + resolver +
" " + safeToString(agent) +
" " + safeToString(bureau.clientObj) +
" " + safeToString(client));
" " + StringUtil.toString(agent) +
" " + StringUtil.toString(bureau.clientObj) +
" " + StringUtil.toString(client));
return null;
}
@@ -447,9 +515,16 @@ public class BureauRegistry
}
}
protected String _serverUrl;
// More readable generic map creation
// TODO: add to library or use existing
protected static <K, V> HashMap<K, V> hashMap ()
{
return new HashMap<K, V>();
}
protected String _serverNameAndPort;
protected InvocationManager _invmgr;
protected RootDObjectManager _omgr;
protected Map<String, Launcher> _launchers = new HashMap<String, Launcher>();
protected Map<String, Bureau> _bureaus = new HashMap<String, Bureau>();
protected Map<String, CommandGenerator> _generators = hashMap();
protected Map<String, Bureau> _bureaus = hashMap();
}