Doris the refactorasaurus. Restructured the whole client object resolution

process so that it can be done by other entities than just the client
management services. Coordination between these parties is managed so that
no toes are stepped on in the course of loading and unloading clients and
everything is generally much nicer.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1086 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2002-03-05 05:33:25 +00:00
parent 38ca708a4f
commit 9e16e87a69
13 changed files with 456 additions and 228 deletions
@@ -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; package com.threerings.crowd.server;
import com.threerings.presents.server.PresentsClient; import com.threerings.presents.server.PresentsClient;
import com.threerings.crowd.data.BodyObject;
/** /**
* The crowd client extends the presents client and does some * The crowd client extends the presents client but doesn't really do
* initializations necessary for the crowd services. * 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 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;
} }
@@ -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;
}
}
@@ -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; package com.threerings.crowd.server;
@@ -34,11 +34,11 @@ public class CrowdServer extends PresentsServer
// bind the crowd server config into the namespace // bind the crowd server config into the namespace
config.bindProperties(CONFIG_KEY, CONFIG_PATH, true); 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); clmgr.setClientClass(CrowdClient.class);
// configure the client to use the body object // configure the client manager to use our resolver
clmgr.setClientObjectClass(BodyObject.class); clmgr.setClientResolverClass(CrowdClientResolver.class);
// create our place registry // create our place registry
plreg = new PlaceRegistry(config, invmgr, omgr); 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 * The server maintains a mapping of username to body object for all
* all active users on the server. This should only be called from the * active users on the server. This should only be called from the
* dobjmgr thread. * dobjmgr thread.
*/ */
public static BodyObject lookupBody (String username) public static BodyObject lookupBody (String username)
{ {
return (BodyObject)_bodymap.get(username); return (BodyObject)clmgr.getClientObject(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);
} }
public static void main (String[] args) public static void main (String[] args)
@@ -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; package com.threerings.micasa.simulator.server;
@@ -23,10 +23,4 @@ public class SimpleServer extends CrowdServer
SimulatorManager simmgr = new SimulatorManager(); SimulatorManager simmgr = new SimulatorManager();
simmgr.init(config, invmgr, plreg, clmgr, omgr, this); simmgr.init(config, invmgr, plreg, clmgr, omgr, this);
} }
// documentation inherited
public void fakeBodyMapping (String username, BodyObject bodobj)
{
_bodymap.put(username, bodobj);
}
} }
@@ -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; package com.threerings.micasa.simulator.server;
@@ -7,12 +7,10 @@ import java.util.ArrayList;
import com.samskivert.util.Config; import com.samskivert.util.Config;
import com.threerings.presents.dobj.DObject; import com.threerings.presents.data.ClientObject;
import com.threerings.presents.dobj.ObjectAccessException;
import com.threerings.presents.dobj.Subscriber;
import com.threerings.presents.dobj.RootDObjectManager; import com.threerings.presents.dobj.RootDObjectManager;
import com.threerings.presents.server.ClientManager; import com.threerings.presents.server.ClientManager;
import com.threerings.presents.server.ClientResolutionListener;
import com.threerings.presents.server.InvocationManager; import com.threerings.presents.server.InvocationManager;
import com.threerings.presents.server.InvocationProvider; 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 // cast the place to the game object for the game we're creating
_gobj = (GameObject)place; _gobj = (GameObject)place;
// create a subscriber to await creation of each simulant body // resolve the simulant body objects
// object ClientResolutionListener listener = new ClientResolutionListener()
Subscriber sub = new Subscriber() { {
public void objectAvailable (DObject object) public void clientResolved (String username, ClientObject clobj)
{ {
// set up the simulant's body object // hold onto the body object for later game creation
BodyObject bobj = (BodyObject)object; _sims.add(clobj);
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);
// create the game if we've received all body objects // create the game if we've received all body objects
if (_sims.size() == (_playerCount - 1)) { if (_sims.size() == (_playerCount - 1)) {
@@ -137,18 +128,17 @@ public class SimulatorManager
} }
} }
public void requestFailed ( public void resolutionFailed (String username, Exception cause)
int oid, ObjectAccessException cause)
{ {
Log.warning("Unable to create simulant body object " + Log.warning("Unable to create simulant body object " +
"[error=" + cause + "]."); "[error=" + cause + "].");
} }
}; };
// fire off simulant body object creation requests // resolve client objects for all of our simulants
Class simobjClass = _clmgr.getClientObjectClass();
for (int ii = 1; ii < _playerCount; ii++) { for (int ii = 1; ii < _playerCount; ii++) {
_omgr.createObject(simobjClass, sub); String username = "simulant" + (_sims.size() + 1);
_clmgr.resolveClientObject(username, listener);
} }
} }
@@ -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; package com.threerings.micasa.simulator.server;
@@ -25,10 +25,4 @@ public interface SimulatorServer
* primary business. * primary business.
*/ */
public void run (); 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);
} }
@@ -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.AuthResponse;
import com.threerings.presents.net.AuthResponseData; 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 * The authenticator is a pluggable component of the authentication
* framework. The base class handles the basic mechanics of authentication * framework. The base class handles the basic mechanics of authentication
@@ -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; package com.threerings.presents.server;
@@ -26,6 +26,10 @@ import com.threerings.presents.server.net.*;
*/ */
public class ClientManager implements ConnectionObserver public class ClientManager implements ConnectionObserver
{ {
/**
* Constructs a client manager that will interact with the supplied
* connection manager.
*/
public ClientManager (ConnectionManager conmgr) public ClientManager (ConnectionManager conmgr)
{ {
// register ourselves as a connection observer // 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 * Instructs the client manager to construct instances of this derived
* class of <code>PresentsClient</code> to managed newly accepted client * class of {@link PresentsClient} to managed newly accepted client
* connections. * connections.
*
* @see PresentsClient
*/ */
public void setClientClass (Class clientClass) public void setClientClass (Class clientClass)
{ {
@@ -54,35 +56,103 @@ public class ClientManager implements ConnectionObserver
} }
/** /**
* Instructs the client to create an instance of this * Instructs the client to use instances of this {@link
* <code>ClientObject</code> derived class when creating the * ClientResolver} derived class when resolving clients in preparation
* distributed object that corresponds to a particular client session. * for starting a client session.
*
* @see com.threerings.presents.data.ClientObject
*/ */
public void setClientObjectClass (Class clobjClass) public void setClientResolverClass (Class clrClass)
{ {
// sanity check // sanity check
if (!ClientObject.class.isAssignableFrom(clobjClass)) { if (!ClientResolver.class.isAssignableFrom(clrClass)) {
Log.warning("Requested to use client object class that does " + Log.warning("Requested to use client resolver class that does " +
"not derive from ClientObject " + "not derive from ClientResolver " +
"[class=" + clobjClass.getName() + "]."); "[class=" + clrClass.getName() + "].");
return;
}
// make a note of it } else {
_clobjClass = clobjClass; // make a note of it
_clrClass = clrClass;
}
} }
/** /**
* Returns the class that should be used when creating a distributed * Returns the client object associated with the specified username.
* object to accompany a particular client session. In general, this * This will return null unless the client object is resolved for some
* is only used by the <code>PresentsClient</code> object when it is * reason (like they are logged on).
* setting up a client's session for the first time.
*/ */
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 // documentation inherited
@@ -98,7 +168,7 @@ public class ClientManager implements ConnectionObserver
if (client != null) { if (client != null) {
Log.info("Session resumed [username=" + username + Log.info("Session resumed [username=" + username +
", conn=" + conn + "]."); ", conn=" + conn + "].");
client.resumeSession(conn, rsp.getAuthInfo()); client.resumeSession(conn);
} else { } else {
Log.info("Session initiated [username=" + username + 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 // create a new client and stick'em in the table
try { try {
client = (PresentsClient)_clientClass.newInstance(); client = (PresentsClient)_clientClass.newInstance();
client.startSession(this, username, conn, rsp.getAuthInfo()); client.startSession(this, username, conn);
_usermap.put(username, client); _usermap.put(username, client);
} catch (Exception e) { } catch (Exception e) {
Log.warning("Failed to instantiate client instance to " + Log.warning("Failed to instantiate client instance to " +
"manage new client connection " + "manage new client connection " +
@@ -162,26 +233,39 @@ public class ClientManager implements ConnectionObserver
*/ */
synchronized void clientDidEndSession (PresentsClient client) synchronized void clientDidEndSession (PresentsClient client)
{ {
String username = client.getUsername();
// remove the client from the username map // 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) { if (rc == null) {
Log.warning("Unregistered client ended session " + Log.warning("Unregistered client ended session " +
"[client=" + client + "]."); "[client=" + client + "].");
} else if (rc != client) { } else if (rc != client) {
Log.warning("Different clients with same username!? " + Log.warning("Different clients with same username!? " +
"[c1=" + rc + ", c2=" + client + "]."); "[c1=" + rc + ", c2=" + client + "].");
} else { } else {
Log.info("Ending session [client=" + client + "]."); Log.info("Ending session [client=" + client + "].");
} }
} }
/** A mapping from usernames to client instances. */
protected HashMap _usermap = new HashMap(); protected HashMap _usermap = new HashMap();
/** A mapping from connections to client instances. */
protected HashMap _conmap = new HashMap(); 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 _clientClass = PresentsClient.class;
protected Class _clobjClass = ClientObject.class;
/** The client resolver class in use. */
protected Class _clrClass = ClientResolver.class;
} }
@@ -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);
}
@@ -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;
}
@@ -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; package com.threerings.presents.server;
import com.threerings.presents.Log; import com.threerings.presents.Log;
import com.threerings.presents.net.AuthResponse; import com.threerings.presents.net.AuthResponse;
import com.threerings.presents.net.AuthResponseData; import com.threerings.presents.net.AuthResponseData;
import com.threerings.presents.server.net.Authenticator;
import com.threerings.presents.server.net.AuthingConnection; import com.threerings.presents.server.net.AuthingConnection;
/** /**
@@ -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; 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. * conmgr thread and therefore also need not be synchronized.
*/ */
public class PresentsClient public class PresentsClient
implements Subscriber, EventListener, MessageHandler implements Subscriber, EventListener, MessageHandler,
ClientResolutionListener
{ {
/** /**
* Returns the username with which this client instance is associated. * 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 * Initializes this client instance with the specified username,
* connection instance and begins a client session. * connection instance and client object and begins a client session.
*/ */
protected void startSession ( protected void startSession (
ClientManager cmgr, String username, Connection conn, ClientManager cmgr, String username, Connection conn)
final Object authInfo)
{ {
_cmgr = cmgr; _cmgr = cmgr;
_username = username; _username = username;
setConnection(conn); setConnection(conn);
// create our client object and bootstrap the client once we've // resolve our client object before we get fully underway
// got it cmgr.resolveClientObject(username, this);
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);
} }
/** // documentation inherited from interface
* Called when we receive our client object from the dobjmgr. We go public void clientResolved (String username, ClientObject clobj)
* through this synchronized method to ensure that the client object
* is visible to the conmgr.
*/
protected synchronized void setClientObject (ClientObject clobj)
{ {
// we'll be keeping this bad boy
_clobj = clobj; _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 * authenticates as this already established client. This must only be
* called from the congmr thread. * called from the congmr thread.
*/ */
protected void resumeSession (Connection conn, final Object authInfo) protected void resumeSession (Connection conn)
{ {
Connection oldconn = getConnection(); Connection oldconn = getConnection();
@@ -145,7 +139,7 @@ public class PresentsClient
{ {
// now that we're on the dobjmgr thread we can resume our // now that we're on the dobjmgr thread we can resume our
// session resumption // session resumption
finishResumeSession(authInfo); finishResumeSession();
return false; return false;
} }
}; };
@@ -157,10 +151,10 @@ public class PresentsClient
* resumption. We call some call backs and send the bootstrap info to * resumption. We call some call backs and send the bootstrap info to
* the client. * the client.
*/ */
protected void finishResumeSession (Object authInfo) protected void finishResumeSession ()
{ {
// let derived classes do any session resuming // let derived classes do any session resuming
sessionWillResume(authInfo); sessionWillResume();
// send off a bootstrap notification immediately because we've // send off a bootstrap notification immediately because we've
// already got our client object // already got our client object
@@ -170,38 +164,20 @@ public class PresentsClient
} }
/** /**
* Requests that the client session be terminated. This is normally * Forcibly terminates a client's session. This must be called from
* invoked as a result of a logoff request by the client. It should * the dobjmgr thread.
* only be invoked from the conmgr thread.
*/ */
public void endSession () public void endSession ()
{ {
// close our connection (which results in everything being // queue up a request for our connection to be closed (if we have
// properly unregistered) // a connection, that is)
Connection conn = getConnection(); Connection conn = getConnection();
if (conn != null) { if (conn != null) {
conn.close(); PresentsServer.conmgr.closeConnection(conn);
} else {
Log.info("Unable to close connection for logoff request " +
"[client=" + this + "].");
} }
// then let the client manager know what's up
_cmgr.clientDidEndSession(this);
// we need to get onto the distributed object thread so that we // and clean up after ourselves
// can finalize the ending of the session. we do so by posting a sessionDidEnd();
// 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);
} }
/** /**
@@ -260,11 +236,8 @@ public class PresentsClient
* <p><em>Note:</em> This function will be called on the dobjmgr * <p><em>Note:</em> This function will be called on the dobjmgr
* thread which means that object manipulations are OK, but client * thread which means that object manipulations are OK, but client
* instance manipulations must done carefully. * 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
* <p><em>Note:</em> This function will be called on the dobjmgr * <p><em>Note:</em> This function will be called on the dobjmgr
* thread which means that object manipulations are OK, but client * thread which means that object manipulations are OK, but client
* instance manipulations must done carefully. * 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 * Called when the client session ends (either because the client
* logged off or because the server forcibly terminated the session). * logged off or because the server forcibly terminated the session).
* Derived classes that override this method should be sure to call * Derived classes that override this method should be sure to call
* <code>super.sessionDidTerminate</code>. * <code>super.sessionDidEnd</code>.
* *
* <p><em>Note:</em> This function will be called on the dobjmgr * <p><em>Note:</em> This function will be called on the dobjmgr
* thread which means that object manipulations are OK, but client * thread which means that object manipulations are OK, but client
* instance manipulations must done carefully. * 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 // destroy the client object
PresentsServer.omgr.destroyObject(_clobj.getOid()); PresentsServer.omgr.destroyObject(_clobj.getOid());
} }
@@ -363,20 +336,15 @@ public class PresentsClient
setConnection(null); setConnection(null);
// clear out our subscriptions. we need to do this on the dobjmgr // 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 // thread. it is important that we do this *after* we clear out
// that we do this *after* we clear out our connection reference. // our connection reference. once the connection ref is null, no
// once the connection ref is null, no more subscriptions will be // more subscriptions will be processed (even those that were
// processed (even those that were queued up before the connection // queued up before the connection went away)
// went away) PresentsServer.omgr.postUnit(new Runnable() {
DEvent event = new DEvent(0) public void run () {
{
public boolean applyToObject (DObject target)
{
clearSubscrips(); clearSubscrips();
return false;
} }
}; });
PresentsServer.omgr.postEvent(event);
} }
/** /**
@@ -595,15 +563,26 @@ public class PresentsClient
/** /**
* Processes logoff requests. * Processes logoff requests.
*/ */
protected static class LogoffDispatcher implements MessageDispatcher protected static class LogoffDispatcher
implements MessageDispatcher, Runnable
{ {
public void dispatch (PresentsClient client, UpstreamMessage msg) public void dispatch (PresentsClient client, UpstreamMessage msg)
{ {
Log.info("Client requested logoff " + Log.info("Client requested logoff " +
"[client=" + client + "]."); "[client=" + client + "].");
// request that the client end the session _client = client;
client.endSession(); // 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; protected ClientManager _cmgr;
@@ -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; package com.threerings.presents.server.net;
@@ -12,11 +12,15 @@ import com.samskivert.util.*;
import com.threerings.presents.Log; import com.threerings.presents.Log;
import com.threerings.presents.client.Client; import com.threerings.presents.client.Client;
import com.threerings.presents.io.FramingOutputStream; import com.threerings.presents.io.FramingOutputStream;
import com.threerings.presents.io.TypedObjectFactory; import com.threerings.presents.io.TypedObjectFactory;
import com.threerings.presents.net.AuthRequest; import com.threerings.presents.net.AuthRequest;
import com.threerings.presents.net.AuthResponse; import com.threerings.presents.net.AuthResponse;
import com.threerings.presents.net.DownstreamMessage; import com.threerings.presents.net.DownstreamMessage;
import com.threerings.presents.server.Authenticator;
import com.threerings.presents.server.PresentsServer; 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 * Notifies the connection observers of a connection event. Used
* internally. * internally.
@@ -146,6 +168,12 @@ public class ConnectionManager extends LoopingThread
*/ */
protected void iterate () 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 // send any messages that are waiting on the outgoing queue
Tuple tup; Tuple tup;
while ((tup = (Tuple)_outq.getNonBlocking()) != null) { while ((tup = (Tuple)_outq.getNonBlocking()) != null) {
@@ -303,16 +331,6 @@ public class ConnectionManager extends LoopingThread
notifyObservers(CONNECTION_CLOSED, conn, null, null); 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 int _port;
protected Authenticator _author; protected Authenticator _author;
protected SelectSet _selset; protected SelectSet _selset;
@@ -320,6 +338,8 @@ public class ConnectionManager extends LoopingThread
protected NonblockingServerSocket _listener; protected NonblockingServerSocket _listener;
protected SelectItem _litem; protected SelectItem _litem;
protected Queue _deathq = new Queue();
protected Queue _outq = new Queue(); protected Queue _outq = new Queue();
protected FramingOutputStream _framer; protected FramingOutputStream _framer;
protected DataOutputStream _dout; protected DataOutputStream _dout;