Widened, fixed documentation and nixed unneeded runnable posting for

clientSessionDidEnd() which is called on the dobjmgr thread directly.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4680 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2007-05-04 02:11:33 +00:00
parent bd8ad47153
commit 69485a88ee
2 changed files with 83 additions and 128 deletions
@@ -41,15 +41,13 @@ import com.threerings.presents.net.Credentials;
import com.threerings.presents.server.net.*; import com.threerings.presents.server.net.*;
/** /**
* The client manager is responsible for managing the clients (surprise, * The client manager is responsible for managing the clients (surprise, surprise) which are
* surprise) which are slightly more than just connections. Clients * slightly more than just connections. Clients persist in the absence of connections in case a
* persist in the absence of connections in case a user goes bye bye * user goes bye bye unintentionally and wants to reconnect and continue their session.
* unintentionally and wants to reconnect and continue their session.
* *
* <p> The client manager operates with thread safety because it is called * <p> The client manager operates with thread safety because it is called both from the conmgr
* both from the conmgr thread (to notify of connections showing up or * thread (to notify of connections showing up or going away) and from the dobjmgr thread (when
* going away) and from the dobjmgr thread (when clients are given the * clients are given the boot for application-defined reasons).
* boot for application-defined reasons).
*/ */
public class ClientManager public class ClientManager
implements ConnectionObserver, ClientResolutionListener, implements ConnectionObserver, ClientResolutionListener,
@@ -72,35 +70,32 @@ public class ClientManager
} }
/** /**
* Used by entites that wish to track when clients initiate and end * Used by entites that wish to track when clients initiate and end sessions on this server.
* sessions on this server.
*/ */
public static interface ClientObserver public static interface ClientObserver
{ {
/** /**
* Called when a client has authenticated and been resolved and has * Called when a client has authenticated and been resolved and has started their session.
* started their session.
*/ */
public void clientSessionDidStart (PresentsClient client); public void clientSessionDidStart (PresentsClient client);
/** /**
* Called when a client has logged off or been forcibly logged off due * Called when a client has logged off or been forcibly logged off due to inactivity and
* to inactivity and has thus ended their session. * has thus ended their session.
*/ */
public void clientSessionDidEnd (PresentsClient client); public void clientSessionDidEnd (PresentsClient client);
} }
/** /**
* Constructs a client manager that will interact with the supplied * Constructs a client manager that will interact with the supplied connection manager.
* connection manager.
*/ */
public ClientManager (ConnectionManager conmgr) public ClientManager (ConnectionManager conmgr)
{ {
// register ourselves as a connection observer // register ourselves as a connection observer
conmgr.addConnectionObserver(this); conmgr.addConnectionObserver(this);
// start up an interval that will check for expired clients and // start up an interval that will check for expired clients and flush them from the bowels
// flush them from the bowels of the server // of the server
new Interval(PresentsServer.omgr) { new Interval(PresentsServer.omgr) {
public void expired () { public void expired () {
flushClients(); flushClients();
@@ -115,8 +110,7 @@ public class ClientManager
// documentation inherited from interface // documentation inherited from interface
public void shutdown () public void shutdown ()
{ {
Log.info("Client manager shutting down " + Log.info("Client manager shutting down [ccount=" + _usermap.size() + "].");
"[ccount=" + _usermap.size() + "].");
// inform all of our clients that they are being shut down // inform all of our clients that they are being shut down
for (Iterator iter = _usermap.values().iterator(); iter.hasNext(); ) { for (Iterator iter = _usermap.values().iterator(); iter.hasNext(); ) {
@@ -124,17 +118,16 @@ public class ClientManager
try { try {
pc.shutdown(); pc.shutdown();
} catch (Exception e) { } catch (Exception e) {
Log.warning("Client choked in shutdown() [client=" + Log.warning(
StringUtil.safeToString(pc) + "]."); "Client choked in shutdown() [client=" + StringUtil.safeToString(pc) + "].");
Log.logStackTrace(e); Log.logStackTrace(e);
} }
} }
} }
/** /**
* Configures the client manager with a factory for creating {@link * Configures the client manager with a factory for creating {@link PresentsClient} and {@link
* PresentsClient} and {@link ClientResolver} classes for authenticated * ClientResolver} classes for authenticated client connections.
* client connections.
*/ */
public void setClientFactory (ClientFactory factory) public void setClientFactory (ClientFactory factory)
{ {
@@ -182,8 +175,7 @@ public class ClientManager
} }
/** /**
* Registers an observer that will be notified when clients start and end * Registers an observer that will be notified when clients start and end their sessions.
* their sessions.
*/ */
public void addClientObserver (ClientObserver observer) public void addClientObserver (ClientObserver observer)
{ {
@@ -191,8 +183,7 @@ public class ClientManager
} }
/** /**
* Removes an observer previously registered with {@link * Removes an observer previously registered with {@link #addClientObserver}.
* #addClientObserver}.
*/ */
public void removeClientObserver (ClientObserver observer) public void removeClientObserver (ClientObserver observer)
{ {
@@ -200,9 +191,8 @@ public class ClientManager
} }
/** /**
* Returns the client instance that manages the client session for the * Returns the client instance that manages the client session for the specified authentication
* specified authentication username or null if that client is not * username or null if that client is not currently connected to the server.
* currently connected to the server.
*/ */
public PresentsClient getClient (Name authUsername) public PresentsClient getClient (Name authUsername)
{ {
@@ -210,9 +200,8 @@ public class ClientManager
} }
/** /**
* Returns the client object associated with the specified username. * Returns the client object associated with the specified username. This will return null
* This will return null unless the client object is resolved for some * unless the client object is resolved for some reason (like they are logged on).
* reason (like they are logged on).
*/ */
public ClientObject getClientObject (Name username) public ClientObject getClientObject (Name username)
{ {
@@ -220,8 +209,8 @@ public class ClientManager
} }
/** /**
* Resolves the specified client, applies the supplied client * Resolves the specified client, applies the supplied client operation to them and releases
* operation to them and releases the client. * the client.
*/ */
public void applyToClient (Name username, ClientOp clop) public void applyToClient (Name username, ClientOp clop)
{ {
@@ -229,13 +218,11 @@ public class ClientManager
} }
/** /**
* Requests that the client object for the specified user be resolved. * Requests that the client object for the specified user be resolved. If the client object is
* If the client object is already resolved, the request will be * already resolved, the request will be processed immediately, otherwise the appropriate
* processed immediately, otherwise the appropriate client object will * client object will be instantiated and populated by the registered client resolver (which
* be instantiated and populated by the registered client resolver * may involve talking to databases). <em>Note:</em> this <b>must</b> be paired with a call to
* (which may involve talking to databases). <em>Note:</em> this * {@link #releaseClientObject} when the caller is finished with the client object.
* <b>must</b> be paired with a call to {@link #releaseClientObject}
* when the caller is finished with the client object.
*/ */
public synchronized void resolveClientObject ( public synchronized void resolveClientObject (
Name username, final ClientResolutionListener listener) Name username, final ClientResolutionListener listener)
@@ -257,18 +244,16 @@ public class ClientManager
} }
try { try {
// create a client resolver instance which will create our // create a client resolver instance which will create our client object, populate it
// client object, populate it and notify the listeners // and notify the listeners
clr = _factory.createClientResolver(username); clr = _factory.createClientResolver(username);
clr.init(username); clr.init(username);
clr.addResolutionListener(this); clr.addResolutionListener(this);
clr.addResolutionListener(listener); clr.addResolutionListener(listener);
_penders.put(username, clr); _penders.put(username, clr);
// create and register our client object and give it back to the // create and register our client object and give it back to the client resolver
// client resolver clr.objectAvailable(PresentsServer.omgr.registerObject(clr.createClientObject()));
clr.objectAvailable(
PresentsServer.omgr.registerObject(clr.createClientObject()));
} catch (Exception e) { } catch (Exception e) {
// let the listener know that we're hosed // let the listener know that we're hosed
@@ -277,22 +262,19 @@ public class ClientManager
} }
/** /**
* Releases a client object that was obtained via a call to {@link * Releases a client object that was obtained via a call to {@link #resolveClientObject}. If
* #resolveClientObject}. If this caller is the last reference, the * this caller is the last reference, the object will be flushed and destroyed.
* object will be flushed and destroyed.
*/ */
public void releaseClientObject (Name username) public void releaseClientObject (Name username)
{ {
ClientObject clobj = _objmap.get(username); ClientObject clobj = _objmap.get(username);
if (clobj == null) { if (clobj == null) {
Log.warning("Requested to release unmapped client object " + Log.warning("Requested to release unmapped client object [username=" + username + "].");
"[username=" + username + "].");
Thread.dumpStack(); Thread.dumpStack();
return; return;
} }
// decrement the reference count and stop here if there are // decrement the reference count and stop here if there are remaining references
// remaining references
if (clobj.release()) { if (clobj.release()) {
return; return;
} }
@@ -309,10 +291,10 @@ public class ClientManager
// documentation inherited from interface ClientResolutionListener // documentation inherited from interface ClientResolutionListener
public synchronized void clientResolved (Name username, ClientObject clobj) public synchronized void clientResolved (Name username, ClientObject clobj)
{ {
// because we added ourselves as a client resolution listener, the // because we added ourselves as a client resolution listener, the client object reference
// client object reference count was increased, but we're not a real // count was increased, but we're not a real resolver (who would have to call
// resolver (who would have to call releaseClientObject() to release // releaseClientObject() to release their reference), so we release our reference
// their reference), so we release our reference immediately // immediately
clobj.release(); clobj.release();
// stuff the object into the mapping table // stuff the object into the mapping table
@@ -340,13 +322,11 @@ public class ClientManager
PresentsClient client = getClient(username); PresentsClient client = getClient(username);
if (client != null) { if (client != null) {
Log.info("Resuming session [username=" + username + Log.info("Resuming session [username=" + username + ", conn=" + conn + "].");
", conn=" + conn + "].");
client.resumeSession(req, conn); client.resumeSession(req, conn);
} else { } else {
Log.info("Session initiated [username=" + username + Log.info("Session initiated [username=" + username + ", conn=" + conn + "].");
", conn=" + conn + "].");
// create a new client and stick'em in the table // create a new client and stick'em in the table
client = _factory.createClient(req); client = _factory.createClient(req);
client.startSession(this, req, conn, rsp.authdata); client.startSession(this, req, conn, rsp.authdata);
@@ -360,22 +340,20 @@ public class ClientManager
} }
// documentation inherited // documentation inherited
public synchronized void connectionFailed ( public synchronized void connectionFailed (Connection conn, IOException fault)
Connection conn, IOException fault)
{ {
// remove the client from the connection map // remove the client from the connection map
PresentsClient client = _conmap.remove(conn); PresentsClient client = _conmap.remove(conn);
if (client != null) { if (client != null) {
Log.info("Unmapped failed client [client=" + client + Log.info("Unmapped failed client [client=" + client + ", conn=" + conn +
", conn=" + conn + ", fault=" + fault + "]."); ", fault=" + fault + "].");
// let the client know the connection went away // let the client know the connection went away
client.wasUnmapped(); client.wasUnmapped();
// and let the client know things went haywire // and let the client know things went haywire
client.connectionFailed(fault); client.connectionFailed(fault);
} else if (!(conn instanceof AuthingConnection)) { } else if (!(conn instanceof AuthingConnection)) {
Log.info("Unmapped connection failed? [conn=" + conn + Log.info("Unmapped connection failed? [conn=" + conn + ", fault=" + fault + "].");
", fault=" + fault + "].");
Thread.dumpStack(); Thread.dumpStack();
} }
} }
@@ -386,8 +364,7 @@ public class ClientManager
// remove the client from the connection map // remove the client from the connection map
PresentsClient client = _conmap.remove(conn); PresentsClient client = _conmap.remove(conn);
if (client != null) { if (client != null) {
Log.debug("Unmapped client [client=" + client + Log.debug("Unmapped client [client=" + client + ", conn=" + conn + "].");
", conn=" + conn + "].");
// let the client know the connection went away // let the client know the connection went away
client.wasUnmapped(); client.wasUnmapped();
@@ -398,8 +375,7 @@ public class ClientManager
} }
// documentation inherited from interface PresentsServer.Reporter // documentation inherited from interface PresentsServer.Reporter
public void appendReport ( public void appendReport (StringBuilder report, long now, long sinceLast, boolean reset)
StringBuilder report, long now, long sinceLast, boolean reset)
{ {
report.append("* presents.ClientManager:\n"); report.append("* presents.ClientManager:\n");
report.append("- Sessions: "); report.append("- Sessions: ");
@@ -410,8 +386,8 @@ public class ClientManager
} }
/** /**
* Called by the client instance when it has started its session. This is * Called by the client instance when it has started its session. This is called from the
* called from the dobjmgr thread. * dobjmgr thread.
*/ */
protected void clientSessionDidStart (final PresentsClient client) protected void clientSessionDidStart (final PresentsClient client)
{ {
@@ -425,15 +401,13 @@ public class ClientManager
} }
/** /**
* Called by the client instance when the client requests a logoff. * Called by the client instance when the client requests a logoff. This is called from the
* This is called from the conmgr thread. * dobjmgr thread.
*/ */
protected synchronized void clientSessionDidEnd ( protected void clientSessionDidEnd (final PresentsClient client)
final PresentsClient client)
{ {
// remove the client from the username map // remove the client from the username map
PresentsClient rc = _usermap.remove( PresentsClient rc = _usermap.remove(client.getCredentials().getUsername());
client.getCredentials().getUsername());
// sanity check just because we can // sanity check just because we can
if (rc == null) { if (rc == null) {
@@ -446,33 +420,26 @@ public class ClientManager
Log.info("Ending session " + client + "."); Log.info("Ending session " + client + ".");
} }
// queue up a unit on the dobjmgr thread to notify the client observers // notify the observers that the session is ended
// that the session is ended _clobservers.apply(new ObserverList.ObserverOp<ClientObserver>() {
PresentsServer.omgr.postRunnable(new Runnable() { public boolean apply (ClientObserver observer) {
public void run () { observer.clientSessionDidEnd(client);
_clobservers.apply( return true;
new ObserverList.ObserverOp<ClientObserver>() {
public boolean apply (ClientObserver observer) {
observer.clientSessionDidEnd(client);
return true;
}
});
} }
}); });
} }
/** /**
* Called once per minute to check for clients that have been * Called once per minute to check for clients that have been disconnected too long and
* disconnected too long and forcibly end their sessions. * forcibly end their sessions.
*/ */
protected void flushClients () protected void flushClients ()
{ {
ArrayList<PresentsClient> victims = null; ArrayList<PresentsClient> victims = null;
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
// first build a list of our victims (we can't flush clients // first build a list of our victims (we can't flush clients directly while iterating due
// directly while iterating due to risk of a // to risk of a ConcurrentModificationException)
// ConcurrentModificationException)
for (PresentsClient client : _usermap.values()) { for (PresentsClient client : _usermap.values()) {
if (client.checkExpired(now)) { if (client.checkExpired(now)) {
if (victims == null) { if (victims == null) {
@@ -485,13 +452,11 @@ public class ClientManager
if (victims != null) { if (victims != null) {
for (PresentsClient client : victims) { for (PresentsClient client : victims) {
try { try {
Log.info("Client expired, ending session " + Log.info("Client expired, ending session [client=" + client +
"[client=" + client + ", dtime=" + (now-client.getNetworkStamp()) + "ms].");
", dtime=" + (now-client.getNetworkStamp()) + "ms].");
client.endSession(); client.endSession();
} catch (Exception e) { } catch (Exception e) {
Log.warning("Choke while flushing client " + Log.warning("Choke while flushing client [victim=" + client + "].");
"[victim=" + client + "].");
Log.logStackTrace(e); Log.logStackTrace(e);
} }
} }
@@ -502,20 +467,16 @@ public class ClientManager
protected class ClientOpResolver protected class ClientOpResolver
implements ClientResolutionListener implements ClientResolutionListener
{ {
public ClientOpResolver (ClientOp clop) public ClientOpResolver (ClientOp clop) {
{
_clop = clop; _clop = clop;
} }
// documentation inherited from interface public void clientResolved (Name username, ClientObject clobj) {
public void clientResolved (Name username, ClientObject clobj)
{
try { try {
_clop.apply(clobj); _clop.apply(clobj);
} catch (Exception e) { } catch (Exception e) {
Log.warning("Client op failed [username=" + username + Log.warning("Client op failed [username=" + username + ", clop=" + _clop + "].");
", clop=" + _clop + "].");
Log.logStackTrace(e); Log.logStackTrace(e);
} finally { } finally {
@@ -523,9 +484,7 @@ public class ClientManager
} }
} }
// documentation inherited from interface public void resolutionFailed (Name username, Exception reason) {
public void resolutionFailed (Name username, Exception reason)
{
_clop.resolutionFailed(reason); _clop.resolutionFailed(reason);
} }
@@ -533,20 +492,16 @@ public class ClientManager
} }
/** A mapping from auth username to client instances. */ /** A mapping from auth username to client instances. */
protected HashMap<Name,PresentsClient> _usermap = protected HashMap<Name,PresentsClient> _usermap = new HashMap<Name,PresentsClient>();
new HashMap<Name,PresentsClient>();
/** A mapping from connections to client instances. */ /** A mapping from connections to client instances. */
protected HashMap<Connection,PresentsClient> _conmap = protected HashMap<Connection,PresentsClient> _conmap = new HashMap<Connection,PresentsClient>();
new HashMap<Connection,PresentsClient>();
/** A mapping from usernames to client object instances. */ /** A mapping from usernames to client object instances. */
protected HashMap<Name,ClientObject> _objmap = protected HashMap<Name,ClientObject> _objmap = new HashMap<Name,ClientObject>();
new HashMap<Name,ClientObject>();
/** A mapping of pending client resolvers. */ /** A mapping of pending client resolvers. */
protected HashMap<Name,ClientResolver> _penders = protected HashMap<Name,ClientResolver> _penders = new HashMap<Name,ClientResolver>();
new HashMap<Name,ClientResolver>();
/** The client class in use. */ /** The client class in use. */
protected ClientFactory _factory = ClientFactory.DEFAULT; protected ClientFactory _factory = ClientFactory.DEFAULT;
@@ -346,7 +346,7 @@ public class PresentsClient
} }
} }
// documentation inherited from interface // from interface ClientResolutionListener
public void clientResolved (Name username, ClientObject clobj) public void clientResolved (Name username, ClientObject clobj)
{ {
// we'll be keeping this bad boy // we'll be keeping this bad boy
@@ -360,7 +360,7 @@ public class PresentsClient
_cmgr.clientSessionDidStart(this); _cmgr.clientSessionDidStart(this);
} }
// documentation inherited from interface // from interface ClientResolutionListener
public void resolutionFailed (Name username, Exception reason) public void resolutionFailed (Name username, Exception reason)
{ {
// urk; nothing to do but complain and get the f**k out of dodge // urk; nothing to do but complain and get the f**k out of dodge
@@ -371,7 +371,7 @@ public class PresentsClient
endSession(); endSession();
} }
// documentation inherited from interface // from interface MessageHandler
public void handleMessage (UpstreamMessage message) public void handleMessage (UpstreamMessage message)
{ {
_messagesIn++; // count 'em up! _messagesIn++; // count 'em up!
@@ -403,7 +403,7 @@ public class PresentsClient
disp.dispatch(this, message); disp.dispatch(this, message);
} }
// documentation inherited from interface // from interface ProxySubscriber
public void objectAvailable (DObject object) public void objectAvailable (DObject object)
{ {
if (postMessage(new ObjectResponse<DObject>(object))) { if (postMessage(new ObjectResponse<DObject>(object))) {
@@ -415,13 +415,13 @@ public class PresentsClient
} }
} }
// documentation inherited from interface // from interface ProxySubscriber
public void requestFailed (int oid, ObjectAccessException cause) public void requestFailed (int oid, ObjectAccessException cause)
{ {
postMessage(new FailureResponse(oid)); postMessage(new FailureResponse(oid));
} }
// documentation inherited from interface // from interface ProxySubscriber
public void eventReceived (DEvent event) public void eventReceived (DEvent event)
{ {
if (event instanceof PresentsDObjectMgr.AccessObjectEvent) { if (event instanceof PresentsDObjectMgr.AccessObjectEvent) {