diff --git a/src/java/com/threerings/crowd/server/CrowdClient.java b/src/java/com/threerings/crowd/server/CrowdClient.java index 04e48eabc..a8a4aaa78 100644 --- a/src/java/com/threerings/crowd/server/CrowdClient.java +++ b/src/java/com/threerings/crowd/server/CrowdClient.java @@ -1,45 +1,16 @@ // -// $Id: CrowdClient.java,v 1.7 2002/02/20 23:35:42 mdb Exp $ +// $Id: CrowdClient.java,v 1.8 2002/03/05 05:33:25 mdb Exp $ package com.threerings.crowd.server; import com.threerings.presents.server.PresentsClient; -import com.threerings.crowd.data.BodyObject; /** - * The crowd client extends the presents client and does some - * initializations necessary for the crowd services. + * The crowd client extends the presents client but doesn't really do + * anything at present. It exists mainly so that implementation systems + * will extend it and ensure that we have the option of adding + * functionality here in the future. */ public class CrowdClient extends PresentsClient { - protected void sessionWillStart (Object authInfo) - { - super.sessionWillStart(authInfo); - - // cast our client object to a body object - _bodobj = (BodyObject)_clobj; - - // and configure our username - _bodobj.setUsername(_username); - - // register our body object mapping - CrowdServer.mapBody(_username, _bodobj); - } - - protected void sessionWillResume (Object authInfo) - { - super.sessionWillResume(authInfo); - - // nothing to do here presently - } - - protected void sessionDidTerminate () - { - super.sessionDidTerminate(); - - // unregister our body object mapping - CrowdServer.unmapBody(_username); - } - - protected BodyObject _bodobj; } diff --git a/src/java/com/threerings/crowd/server/CrowdClientResolver.java b/src/java/com/threerings/crowd/server/CrowdClientResolver.java new file mode 100644 index 000000000..29eb2a009 --- /dev/null +++ b/src/java/com/threerings/crowd/server/CrowdClientResolver.java @@ -0,0 +1,29 @@ +// +// $Id: CrowdClientResolver.java,v 1.1 2002/03/05 05:33:25 mdb Exp $ + +package com.threerings.crowd.server; + +import com.threerings.presents.data.ClientObject; +import com.threerings.presents.server.ClientResolver; + +import com.threerings.crowd.data.BodyObject; + +/** + * Used to configure crowd-specific client object data. + */ +public class CrowdClientResolver extends ClientResolver +{ + // documentation inherited + public Class getClientObjectClass () + { + return BodyObject.class; + } + + // documentation inherited + protected void resolveClientData (ClientObject clobj) + throws Exception + { + // just fill in the username + ((BodyObject)clobj).username = _username; + } +} diff --git a/src/java/com/threerings/crowd/server/CrowdServer.java b/src/java/com/threerings/crowd/server/CrowdServer.java index b25d06ed0..be85b2224 100644 --- a/src/java/com/threerings/crowd/server/CrowdServer.java +++ b/src/java/com/threerings/crowd/server/CrowdServer.java @@ -1,5 +1,5 @@ // -// $Id: CrowdServer.java,v 1.9 2001/12/04 01:02:59 mdb Exp $ +// $Id: CrowdServer.java,v 1.10 2002/03/05 05:33:25 mdb Exp $ package com.threerings.crowd.server; @@ -34,11 +34,11 @@ public class CrowdServer extends PresentsServer // bind the crowd server config into the namespace config.bindProperties(CONFIG_KEY, CONFIG_PATH, true); - // configure the client to use our crowd client + // configure the client manager to use our client clmgr.setClientClass(CrowdClient.class); - // configure the client to use the body object - clmgr.setClientObjectClass(BodyObject.class); + // configure the client manager to use our resolver + clmgr.setClientResolverClass(CrowdClientResolver.class); // create our place registry plreg = new PlaceRegistry(config, invmgr, omgr); @@ -50,31 +50,13 @@ public class CrowdServer extends PresentsServer } /** - * The crowd server maintains a mapping of username to body object for - * all active users on the server. This should only be called from the + * The server maintains a mapping of username to body object for all + * active users on the server. This should only be called from the * dobjmgr thread. */ public static BodyObject lookupBody (String username) { - return (BodyObject)_bodymap.get(username); - } - - /** - * Called by the crowd client to map a username to a particular body - * object. This should only be called from the dobjmgr thread. - */ - protected static void mapBody (String username, BodyObject bodobj) - { - _bodymap.put(username, bodobj); - } - - /** - * Called by the crowd client to unmap a username from a particular - * body object. This should only be called from the dobjmgr thread. - */ - protected static void unmapBody (String username) - { - _bodymap.remove(username); + return (BodyObject)clmgr.getClientObject(username); } public static void main (String[] args) diff --git a/src/java/com/threerings/micasa/simulator/server/SimpleServer.java b/src/java/com/threerings/micasa/simulator/server/SimpleServer.java index 53985895e..5cb0d59fd 100644 --- a/src/java/com/threerings/micasa/simulator/server/SimpleServer.java +++ b/src/java/com/threerings/micasa/simulator/server/SimpleServer.java @@ -1,5 +1,5 @@ // -// $Id: SimpleServer.java,v 1.2 2002/02/05 22:57:10 mdb Exp $ +// $Id: SimpleServer.java,v 1.3 2002/03/05 05:33:25 mdb Exp $ package com.threerings.micasa.simulator.server; @@ -23,10 +23,4 @@ public class SimpleServer extends CrowdServer SimulatorManager simmgr = new SimulatorManager(); simmgr.init(config, invmgr, plreg, clmgr, omgr, this); } - - // documentation inherited - public void fakeBodyMapping (String username, BodyObject bodobj) - { - _bodymap.put(username, bodobj); - } } diff --git a/src/java/com/threerings/micasa/simulator/server/SimulatorManager.java b/src/java/com/threerings/micasa/simulator/server/SimulatorManager.java index 8fd8b251f..c264dfee0 100644 --- a/src/java/com/threerings/micasa/simulator/server/SimulatorManager.java +++ b/src/java/com/threerings/micasa/simulator/server/SimulatorManager.java @@ -1,5 +1,5 @@ // -// $Id: SimulatorManager.java,v 1.6 2002/02/05 22:12:42 mdb Exp $ +// $Id: SimulatorManager.java,v 1.7 2002/03/05 05:33:25 mdb Exp $ package com.threerings.micasa.simulator.server; @@ -7,12 +7,10 @@ import java.util.ArrayList; import com.samskivert.util.Config; -import com.threerings.presents.dobj.DObject; -import com.threerings.presents.dobj.ObjectAccessException; -import com.threerings.presents.dobj.Subscriber; +import com.threerings.presents.data.ClientObject; import com.threerings.presents.dobj.RootDObjectManager; - import com.threerings.presents.server.ClientManager; +import com.threerings.presents.server.ClientResolutionListener; import com.threerings.presents.server.InvocationManager; import com.threerings.presents.server.InvocationProvider; @@ -116,20 +114,13 @@ public class SimulatorManager // cast the place to the game object for the game we're creating _gobj = (GameObject)place; - // create a subscriber to await creation of each simulant body - // object - Subscriber sub = new Subscriber() { - public void objectAvailable (DObject object) + // resolve the simulant body objects + ClientResolutionListener listener = new ClientResolutionListener() + { + public void clientResolved (String username, ClientObject clobj) { - // set up the simulant's body object - BodyObject bobj = (BodyObject)object; - bobj.username = "simulant" + (_sims.size() + 1); - - // map the simulant into the server body set - _simserv.fakeBodyMapping(bobj.username, bobj); - - // hold onto it for later game creation - _sims.add(bobj); + // hold onto the body object for later game creation + _sims.add(clobj); // create the game if we've received all body objects if (_sims.size() == (_playerCount - 1)) { @@ -137,18 +128,17 @@ public class SimulatorManager } } - public void requestFailed ( - int oid, ObjectAccessException cause) + public void resolutionFailed (String username, Exception cause) { Log.warning("Unable to create simulant body object " + "[error=" + cause + "]."); } }; - // fire off simulant body object creation requests - Class simobjClass = _clmgr.getClientObjectClass(); + // resolve client objects for all of our simulants for (int ii = 1; ii < _playerCount; ii++) { - _omgr.createObject(simobjClass, sub); + String username = "simulant" + (_sims.size() + 1); + _clmgr.resolveClientObject(username, listener); } } diff --git a/src/java/com/threerings/micasa/simulator/server/SimulatorServer.java b/src/java/com/threerings/micasa/simulator/server/SimulatorServer.java index 95b0dc986..5a8f8b0e3 100644 --- a/src/java/com/threerings/micasa/simulator/server/SimulatorServer.java +++ b/src/java/com/threerings/micasa/simulator/server/SimulatorServer.java @@ -1,5 +1,5 @@ // -// $Id: SimulatorServer.java,v 1.5 2002/02/05 22:12:42 mdb Exp $ +// $Id: SimulatorServer.java,v 1.6 2002/03/05 05:33:25 mdb Exp $ package com.threerings.micasa.simulator.server; @@ -25,10 +25,4 @@ public interface SimulatorServer * primary business. */ public void run (); - - /** - * Called by the simulator manager to map a username to a particular - * body object. This should only be called from the dobjmgr thread. - */ - public void fakeBodyMapping (String username, BodyObject bodobj); } diff --git a/src/java/com/threerings/presents/server/Authenticator.java b/src/java/com/threerings/presents/server/Authenticator.java index aedc7ffb9..71204a07a 100644 --- a/src/java/com/threerings/presents/server/Authenticator.java +++ b/src/java/com/threerings/presents/server/Authenticator.java @@ -1,11 +1,14 @@ // -// $Id: Authenticator.java,v 1.4 2002/03/05 03:19:18 mdb Exp $ +// $Id: Authenticator.java,v 1.5 2002/03/05 05:33:25 mdb Exp $ -package com.threerings.presents.server.net; +package com.threerings.presents.server; import com.threerings.presents.net.AuthResponse; import com.threerings.presents.net.AuthResponseData; +import com.threerings.presents.server.net.AuthingConnection; +import com.threerings.presents.server.net.ConnectionManager; + /** * The authenticator is a pluggable component of the authentication * framework. The base class handles the basic mechanics of authentication diff --git a/src/java/com/threerings/presents/server/ClientManager.java b/src/java/com/threerings/presents/server/ClientManager.java index 7fe6d74ea..007b4211f 100644 --- a/src/java/com/threerings/presents/server/ClientManager.java +++ b/src/java/com/threerings/presents/server/ClientManager.java @@ -1,5 +1,5 @@ // -// $Id: ClientManager.java,v 1.13 2001/12/03 22:01:57 mdb Exp $ +// $Id: ClientManager.java,v 1.14 2002/03/05 05:33:25 mdb Exp $ package com.threerings.presents.server; @@ -26,6 +26,10 @@ import com.threerings.presents.server.net.*; */ public class ClientManager implements ConnectionObserver { + /** + * Constructs a client manager that will interact with the supplied + * connection manager. + */ public ClientManager (ConnectionManager conmgr) { // register ourselves as a connection observer @@ -34,10 +38,8 @@ public class ClientManager implements ConnectionObserver /** * Instructs the client manager to construct instances of this derived - * class of PresentsClient to managed newly accepted client + * class of {@link PresentsClient} to managed newly accepted client * connections. - * - * @see PresentsClient */ public void setClientClass (Class clientClass) { @@ -54,35 +56,103 @@ public class ClientManager implements ConnectionObserver } /** - * Instructs the client to create an instance of this - * ClientObject derived class when creating the - * distributed object that corresponds to a particular client session. - * - * @see com.threerings.presents.data.ClientObject + * Instructs the client to use instances of this {@link + * ClientResolver} derived class when resolving clients in preparation + * for starting a client session. */ - public void setClientObjectClass (Class clobjClass) + public void setClientResolverClass (Class clrClass) { // sanity check - if (!ClientObject.class.isAssignableFrom(clobjClass)) { - Log.warning("Requested to use client object class that does " + - "not derive from ClientObject " + - "[class=" + clobjClass.getName() + "]."); - return; - } + if (!ClientResolver.class.isAssignableFrom(clrClass)) { + Log.warning("Requested to use client resolver class that does " + + "not derive from ClientResolver " + + "[class=" + clrClass.getName() + "]."); - // make a note of it - _clobjClass = clobjClass; + } else { + // make a note of it + _clrClass = clrClass; + } } /** - * Returns the class that should be used when creating a distributed - * object to accompany a particular client session. In general, this - * is only used by the PresentsClient object when it is - * setting up a client's session for the first time. + * Returns the client object associated with the specified username. + * This will return null unless the client object is resolved for some + * reason (like they are logged on). */ - public Class getClientObjectClass () + public ClientObject getClientObject (String username) { - return _clobjClass; + return (ClientObject)_objmap.get(username); + } + + /** + * Requests that the client object for the specified user be resolved. + * If the client object is already resolved, the request will be + * processed immediately, otherwise the appropriate client object will + * be instantiated and populated by the registered client resolver + * (which may involve talking to databases). + */ + public synchronized void resolveClientObject ( + String username, ClientResolutionListener listener) + { + // look to see if the client object is already resolved + ClientObject clobj = (ClientObject)_objmap.get(username); + if (clobj != null) { + listener.clientResolved(username, clobj); + return; + } + + // look to see if it's currently being resolved + ClientResolver clr = (ClientResolver)_penders.get(username); + if (clr != null) { + // throw this guy onto the bandwagon + clr.addResolutionListener(listener); + return; + } + + try { + // create a client resolver instance which will create our + // client object, populate it and notify the listeners + clr = (ClientResolver)_clrClass.newInstance(); + clr.init(username); + clr.addResolutionListener(listener); + + // request that the appropriate client object be created by + // the dobject manager which starts the whole business off + PresentsServer.omgr.createObject(clr.getClientObjectClass(), clr); + + } catch (Exception e) { + // let the listener know that we're hosed + listener.resolutionFailed(username, e); + } + } + + /** + * Called by the {@link ClientResolver} once a client object has been + * resolved. + */ + protected synchronized void mapClientObject ( + String username, ClientObject clobj) + { + // stuff the object into the mapping table + _objmap.put(username, clobj); + + // and remove the resolution listener + _penders.remove(username); + } + + /** + * If an entity resolves a client object outside the scope of a normal + * client session, it should call this to unmap the client object when + * it's finished. This won't actually unmap the client if someone came + * along and started a session for that client in the meanwhile. + */ + public synchronized void unmapClientObject (String username) + { + // we only remove the mapping if there's not a session in progress + // (which is indicated by a mapping in the usermap table) + if (!_usermap.containsKey(username)) { + _objmap.remove(username); + } } // documentation inherited @@ -98,7 +168,7 @@ public class ClientManager implements ConnectionObserver if (client != null) { Log.info("Session resumed [username=" + username + ", conn=" + conn + "]."); - client.resumeSession(conn, rsp.getAuthInfo()); + client.resumeSession(conn); } else { Log.info("Session initiated [username=" + username + @@ -106,8 +176,9 @@ public class ClientManager implements ConnectionObserver // create a new client and stick'em in the table try { client = (PresentsClient)_clientClass.newInstance(); - client.startSession(this, username, conn, rsp.getAuthInfo()); + client.startSession(this, username, conn); _usermap.put(username, client); + } catch (Exception e) { Log.warning("Failed to instantiate client instance to " + "manage new client connection " + @@ -162,26 +233,39 @@ public class ClientManager implements ConnectionObserver */ synchronized void clientDidEndSession (PresentsClient client) { + String username = client.getUsername(); // remove the client from the username map - PresentsClient rc = (PresentsClient)_usermap.remove(client.getUsername()); + PresentsClient rc = (PresentsClient)_usermap.remove(username); + // and the client object mapping as well + _objmap.remove(username); - // sanity check because we can + // sanity check just because we can if (rc == null) { Log.warning("Unregistered client ended session " + "[client=" + client + "]."); - } else if (rc != client) { Log.warning("Different clients with same username!? " + "[c1=" + rc + ", c2=" + client + "]."); - } else { Log.info("Ending session [client=" + client + "]."); } } + /** A mapping from usernames to client instances. */ protected HashMap _usermap = new HashMap(); + + /** A mapping from connections to client instances. */ protected HashMap _conmap = new HashMap(); + /** A mapping from usernames to client object instances. */ + protected HashMap _objmap = new HashMap(); + + /** A mapping of pending client resolvers. */ + protected HashMap _penders = new HashMap(); + + /** The client class in use. */ protected Class _clientClass = PresentsClient.class; - protected Class _clobjClass = ClientObject.class; + + /** The client resolver class in use. */ + protected Class _clrClass = ClientResolver.class; } diff --git a/src/java/com/threerings/presents/server/ClientResolutionListener.java b/src/java/com/threerings/presents/server/ClientResolutionListener.java new file mode 100644 index 000000000..4ca8f8b46 --- /dev/null +++ b/src/java/com/threerings/presents/server/ClientResolutionListener.java @@ -0,0 +1,25 @@ +// +// $Id: ClientResolutionListener.java,v 1.1 2002/03/05 05:33:25 mdb Exp $ + +package com.threerings.presents.server; + +import com.threerings.presents.data.ClientObject; + +/** + * Entites that wish to resolve client objects must implement this + * interface so as to partake in the asynchronous process of client + * object resolution. + */ +public interface ClientResolutionListener +{ + /** + * Called when resolution completed successfully. + */ + public void clientResolved (String username, ClientObject clobj); + + /** + * Called when resolution fails. + */ + public void resolutionFailed (String username, Exception reason); +} + diff --git a/src/java/com/threerings/presents/server/ClientResolver.java b/src/java/com/threerings/presents/server/ClientResolver.java new file mode 100644 index 000000000..f6a1f922b --- /dev/null +++ b/src/java/com/threerings/presents/server/ClientResolver.java @@ -0,0 +1,158 @@ +// +// $Id: ClientResolver.java,v 1.1 2002/03/05 05:33:25 mdb Exp $ + +package com.threerings.presents.server; + +import java.util.ArrayList; + +import com.threerings.presents.Log; +import com.threerings.presents.data.ClientObject; +import com.threerings.presents.dobj.DObject; +import com.threerings.presents.dobj.ObjectAccessException; +import com.threerings.presents.dobj.Subscriber; +import com.threerings.presents.util.Invoker; + +/** + * Used to resolve client data when a user starts a session (or when some + * other entity needs access to a client object). Implementations will + * want to extend this class and override {@link #resolveClientData}, + * making the necessary database calls and populating the client object + * appropriately. + */ +public class ClientResolver extends Invoker.Unit + implements Subscriber +{ + /** + * Initiailizes this instance. + * + * @param username the username of the user to be resolved. + */ + public void init (String username) + { + _username = username; + } + + /** + * Adds a resolution listener to this active resolver. + */ + public void addResolutionListener (ClientResolutionListener listener) + { + _listeners.add(listener); + } + + /** + * Returns the {@link ClientObject} derived class that should be + * created to kick off the resolution process. + */ + public Class getClientObjectClass () + { + return ClientObject.class; + } + + // documentation inherited + public void objectAvailable (DObject object) + { + // we've got our object, so shunt ourselves over to the invoker + // thread to perform database loading + _clobj = (ClientObject)object; + PresentsServer.invoker.postUnit(this); + } + + // documentation inherited + public void requestFailed (int oid, ObjectAccessException cause) + { + // pass the buck + reportFailure(cause); + } + + // documentation inherited + public boolean invoke () + { + try { + // allow our derived class to do its database loads + resolveClientData(_clobj); + } catch (Exception cause) { + // keep this around until we're back on the dobj thread + _failure = cause; + } + return true; + } + + // documentation inherited + public void handleResult () + { + // if we failed in invoke() report it here + if (_failure != null) { + // destroy the dangling user object + PresentsServer.omgr.destroyObject(_clobj.getOid()); + + // let our listener know that we're hosed + reportFailure(_failure); + + } else { + // otherwise let the client manager know that we're all clear + PresentsServer.clmgr.mapClientObject(_username, _clobj); + + // and let the listeners in on the secret as well + int lcount = _listeners.size(); + for (int i = 0; i < lcount; i++) { + ClientResolutionListener crl = (ClientResolutionListener) + _listeners.get(i); + try { + crl.clientResolved(_username, _clobj); + } catch (Exception e) { + Log.warning("Client resolution listener choked during " + + "resolution notification [crl=" + crl + + ", clobj=" + _clobj + "]."); + Log.logStackTrace(e); + } + } + } + } + + /** + * This method is called on the invoker thread which means that it can + * do things like blocking database requests and generally whatever is + * necessary to load up all the client data that is desired by the + * implentation system. Any exceptions that are thrown will be caught + * and reported as a failure to the client resolution listener. + */ + protected void resolveClientData (ClientObject clobj) + throws Exception + { + // nothing to do by default + } + + /** + * Reports failure to our resolution listeners. + */ + protected void reportFailure (Exception cause) + { + int lcount = _listeners.size(); + for (int i = 0; i < lcount; i++) { + ClientResolutionListener crl = (ClientResolutionListener) + _listeners.get(i); + try { + crl.resolutionFailed(_username, cause); + } catch (Exception e) { + Log.warning("Client resolution listener choked during " + + "failure notification [crl=" + crl + + ", username=" + _username + + ", cause=" + cause + "]."); + Log.logStackTrace(e); + } + } + } + + /** The name of the user whose client object is being resolved. */ + protected String _username; + + /** The entities to notify of success or failure. */ + protected ArrayList _listeners = new ArrayList(); + + /** The resolving client object. */ + protected ClientObject _clobj; + + /** A place to keep an exception around for a moment. */ + protected Exception _failure; +} diff --git a/src/java/com/threerings/presents/server/DummyAuthenticator.java b/src/java/com/threerings/presents/server/DummyAuthenticator.java index d88880962..ae98e7aad 100644 --- a/src/java/com/threerings/presents/server/DummyAuthenticator.java +++ b/src/java/com/threerings/presents/server/DummyAuthenticator.java @@ -1,12 +1,11 @@ // -// $Id: DummyAuthenticator.java,v 1.5 2002/03/05 03:19:18 mdb Exp $ +// $Id: DummyAuthenticator.java,v 1.6 2002/03/05 05:33:25 mdb Exp $ package com.threerings.presents.server; import com.threerings.presents.Log; import com.threerings.presents.net.AuthResponse; import com.threerings.presents.net.AuthResponseData; -import com.threerings.presents.server.net.Authenticator; import com.threerings.presents.server.net.AuthingConnection; /** diff --git a/src/java/com/threerings/presents/server/PresentsClient.java b/src/java/com/threerings/presents/server/PresentsClient.java index 258e3174b..1f29f141b 100644 --- a/src/java/com/threerings/presents/server/PresentsClient.java +++ b/src/java/com/threerings/presents/server/PresentsClient.java @@ -1,5 +1,5 @@ // -// $Id: PresentsClient.java,v 1.27 2001/12/13 05:32:28 mdb Exp $ +// $Id: PresentsClient.java,v 1.28 2002/03/05 05:33:25 mdb Exp $ package com.threerings.presents.server; @@ -52,7 +52,8 @@ import com.threerings.presents.server.net.MessageHandler; * conmgr thread and therefore also need not be synchronized. */ public class PresentsClient - implements Subscriber, EventListener, MessageHandler + implements Subscriber, EventListener, MessageHandler, + ClientResolutionListener { /** * Returns the username with which this client instance is associated. @@ -71,47 +72,40 @@ public class PresentsClient } /** - * Initializes this client instance with the specified username and - * connection instance and begins a client session. + * Initializes this client instance with the specified username, + * connection instance and client object and begins a client session. */ protected void startSession ( - ClientManager cmgr, String username, Connection conn, - final Object authInfo) + ClientManager cmgr, String username, Connection conn) { _cmgr = cmgr; _username = username; setConnection(conn); - // create our client object and bootstrap the client once we've - // got it - Subscriber sub = new Subscriber () - { - public void objectAvailable (DObject object) - { - setClientObject((ClientObject)object); - sessionWillStart(authInfo); - sendBootstrap(); - } - - public void requestFailed (int oid, ObjectAccessException cause) - { - Log.warning("Unable to create client object " + - "[client=" + PresentsClient.this + - ", error=" + cause + "]."); - } - }; - Class clobjClass = _cmgr.getClientObjectClass(); - PresentsServer.omgr.createObject(clobjClass, sub); + // resolve our client object before we get fully underway + cmgr.resolveClientObject(username, this); } - /** - * Called when we receive our client object from the dobjmgr. We go - * through this synchronized method to ensure that the client object - * is visible to the conmgr. - */ - protected synchronized void setClientObject (ClientObject clobj) + // documentation inherited from interface + public void clientResolved (String username, ClientObject clobj) { + // we'll be keeping this bad boy _clobj = clobj; + + // finish up our regular business + sessionWillStart(); + sendBootstrap(); + } + + // documentation inherited from interface + public void resolutionFailed (String username, Exception reason) + { + // urk; nothing to do but complain and get the f**k out of dodge + Log.warning("Unable to resolve client [username=" + username + "]."); + Log.logStackTrace(reason); + + // end the session now to prevent danglage + endSession(); } /** @@ -119,7 +113,7 @@ public class PresentsClient * authenticates as this already established client. This must only be * called from the congmr thread. */ - protected void resumeSession (Connection conn, final Object authInfo) + protected void resumeSession (Connection conn) { Connection oldconn = getConnection(); @@ -145,7 +139,7 @@ public class PresentsClient { // now that we're on the dobjmgr thread we can resume our // session resumption - finishResumeSession(authInfo); + finishResumeSession(); return false; } }; @@ -157,10 +151,10 @@ public class PresentsClient * resumption. We call some call backs and send the bootstrap info to * the client. */ - protected void finishResumeSession (Object authInfo) + protected void finishResumeSession () { // let derived classes do any session resuming - sessionWillResume(authInfo); + sessionWillResume(); // send off a bootstrap notification immediately because we've // already got our client object @@ -170,38 +164,20 @@ public class PresentsClient } /** - * Requests that the client session be terminated. This is normally - * invoked as a result of a logoff request by the client. It should - * only be invoked from the conmgr thread. + * Forcibly terminates a client's session. This must be called from + * the dobjmgr thread. */ public void endSession () { - // close our connection (which results in everything being - // properly unregistered) + // queue up a request for our connection to be closed (if we have + // a connection, that is) Connection conn = getConnection(); if (conn != null) { - conn.close(); - } else { - Log.info("Unable to close connection for logoff request " + - "[client=" + this + "]."); + PresentsServer.conmgr.closeConnection(conn); } - // then let the client manager know what's up - _cmgr.clientDidEndSession(this); - // we need to get onto the distributed object thread so that we - // can finalize the ending of the session. we do so by posting a - // special event - DEvent event = new DEvent(0) - { - public boolean applyToObject (DObject target) - { - // now that we're on the dobjmgr thread we can call into - // our derived classes to finish the session termination - sessionDidTerminate(); - return false; - } - }; - PresentsServer.omgr.postEvent(event); + // and clean up after ourselves + sessionDidEnd(); } /** @@ -260,11 +236,8 @@ public class PresentsClient *

Note: This function will be called on the dobjmgr * thread which means that object manipulations are OK, but client * instance manipulations must done carefully. - * - * @param authInfo optional information provided by the authenticator - * that it obtained during the authentication phase. */ - protected void sessionWillStart (Object authInfo) + protected void sessionWillStart () { } @@ -278,11 +251,8 @@ public class PresentsClient *

Note: This function will be called on the dobjmgr * thread which means that object manipulations are OK, but client * instance manipulations must done carefully. - * - * @param authInfo optional information provided by the authenticator - * that it obtained during the authentication phase. */ - protected void sessionWillResume (Object authInfo) + protected void sessionWillResume () { } @@ -290,14 +260,17 @@ public class PresentsClient * Called when the client session ends (either because the client * logged off or because the server forcibly terminated the session). * Derived classes that override this method should be sure to call - * super.sessionDidTerminate. + * super.sessionDidEnd. * *

Note: This function will be called on the dobjmgr * thread which means that object manipulations are OK, but client * instance manipulations must done carefully. */ - protected void sessionDidTerminate () + protected void sessionDidEnd () { + // then let the client manager know what's up + _cmgr.clientDidEndSession(this); + // destroy the client object PresentsServer.omgr.destroyObject(_clobj.getOid()); } @@ -363,20 +336,15 @@ public class PresentsClient setConnection(null); // clear out our subscriptions. we need to do this on the dobjmgr - // thread. so we do so by posting a special event. it is important - // that we do this *after* we clear out our connection reference. - // once the connection ref is null, no more subscriptions will be - // processed (even those that were queued up before the connection - // went away) - DEvent event = new DEvent(0) - { - public boolean applyToObject (DObject target) - { + // thread. it is important that we do this *after* we clear out + // our connection reference. once the connection ref is null, no + // more subscriptions will be processed (even those that were + // queued up before the connection went away) + PresentsServer.omgr.postUnit(new Runnable() { + public void run () { clearSubscrips(); - return false; } - }; - PresentsServer.omgr.postEvent(event); + }); } /** @@ -595,15 +563,26 @@ public class PresentsClient /** * Processes logoff requests. */ - protected static class LogoffDispatcher implements MessageDispatcher + protected static class LogoffDispatcher + implements MessageDispatcher, Runnable { public void dispatch (PresentsClient client, UpstreamMessage msg) { Log.info("Client requested logoff " + "[client=" + client + "]."); - // request that the client end the session - client.endSession(); + _client = client; + // queue ourselves up to be run on the object manager thread + // where we can safely end the session + PresentsServer.omgr.postUnit(this); } + + public void run () + { + // end the session in a civilized manner + _client.endSession(); + } + + protected PresentsClient _client; } protected ClientManager _cmgr; diff --git a/src/java/com/threerings/presents/server/net/ConnectionManager.java b/src/java/com/threerings/presents/server/net/ConnectionManager.java index c4c8d2fd7..77619b9a6 100644 --- a/src/java/com/threerings/presents/server/net/ConnectionManager.java +++ b/src/java/com/threerings/presents/server/net/ConnectionManager.java @@ -1,5 +1,5 @@ // -// $Id: ConnectionManager.java,v 1.15 2002/03/05 03:19:18 mdb Exp $ +// $Id: ConnectionManager.java,v 1.16 2002/03/05 05:33:25 mdb Exp $ package com.threerings.presents.server.net; @@ -12,11 +12,15 @@ import com.samskivert.util.*; import com.threerings.presents.Log; import com.threerings.presents.client.Client; + import com.threerings.presents.io.FramingOutputStream; import com.threerings.presents.io.TypedObjectFactory; + import com.threerings.presents.net.AuthRequest; import com.threerings.presents.net.AuthResponse; import com.threerings.presents.net.DownstreamMessage; + +import com.threerings.presents.server.Authenticator; import com.threerings.presents.server.PresentsServer; /** @@ -105,6 +109,24 @@ public class ConnectionManager extends LoopingThread } } + /** + * Queues a connection up to be closed on the conmgr thread. + */ + public void closeConnection (Connection conn) + { + _deathq.append(conn); + } + + /** + * Called by the authenticator to indicate that a connection was + * successfully authenticated. + */ + public void connectionDidAuthenticate (Connection conn) + { + // slap this sucker onto the authenticated connections queue + _authq.append(conn); + } + /** * Notifies the connection observers of a connection event. Used * internally. @@ -146,6 +168,12 @@ public class ConnectionManager extends LoopingThread */ protected void iterate () { + // close any connections that have been queued up to die + Connection dconn; + while ((dconn = (Connection)_deathq.getNonBlocking()) != null) { + dconn.close(); + } + // send any messages that are waiting on the outgoing queue Tuple tup; while ((tup = (Tuple)_outq.getNonBlocking()) != null) { @@ -303,16 +331,6 @@ public class ConnectionManager extends LoopingThread notifyObservers(CONNECTION_CLOSED, conn, null, null); } - /** - * Called by the auth manager to indicate that a connection was - * successfully authenticated. - */ - void connectionDidAuthenticate (Connection conn) - { - // slap this sucker onto the authenticated connections queue - _authq.append(conn); - } - protected int _port; protected Authenticator _author; protected SelectSet _selset; @@ -320,6 +338,8 @@ public class ConnectionManager extends LoopingThread protected NonblockingServerSocket _listener; protected SelectItem _litem; + protected Queue _deathq = new Queue(); + protected Queue _outq = new Queue(); protected FramingOutputStream _framer; protected DataOutputStream _dout;