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.*;
/**
* The client manager is responsible for managing the clients (surprise,
* surprise) which are slightly more than just connections. Clients
* persist in the absence of connections in case a user goes bye bye
* unintentionally and wants to reconnect and continue their session.
* The client manager is responsible for managing the clients (surprise, surprise) which are
* slightly more than just connections. Clients persist in the absence of connections in case a
* user goes bye bye unintentionally and wants to reconnect and continue their session.
*
* <p> The client manager operates with thread safety because it is called
* both from the conmgr thread (to notify of connections showing up or
* going away) and from the dobjmgr thread (when clients are given the
* boot for application-defined reasons).
* <p> The client manager operates with thread safety because it is called both from the conmgr
* thread (to notify of connections showing up or going away) and from the dobjmgr thread (when
* clients are given the boot for application-defined reasons).
*/
public class ClientManager
implements ConnectionObserver, ClientResolutionListener,
@@ -72,35 +70,32 @@ public class ClientManager
}
/**
* Used by entites that wish to track when clients initiate and end
* sessions on this server.
* Used by entites that wish to track when clients initiate and end sessions on this server.
*/
public static interface ClientObserver
{
/**
* Called when a client has authenticated and been resolved and has
* started their session.
* Called when a client has authenticated and been resolved and has started their session.
*/
public void clientSessionDidStart (PresentsClient client);
/**
* Called when a client has logged off or been forcibly logged off due
* to inactivity and has thus ended their session.
* Called when a client has logged off or been forcibly logged off due to inactivity and
* has thus ended their session.
*/
public void clientSessionDidEnd (PresentsClient client);
}
/**
* Constructs a client manager that will interact with the supplied
* connection manager.
* Constructs a client manager that will interact with the supplied connection manager.
*/
public ClientManager (ConnectionManager conmgr)
{
// register ourselves as a connection observer
conmgr.addConnectionObserver(this);
// start up an interval that will check for expired clients and
// flush them from the bowels of the server
// start up an interval that will check for expired clients and flush them from the bowels
// of the server
new Interval(PresentsServer.omgr) {
public void expired () {
flushClients();
@@ -115,8 +110,7 @@ public class ClientManager
// documentation inherited from interface
public void shutdown ()
{
Log.info("Client manager shutting down " +
"[ccount=" + _usermap.size() + "].");
Log.info("Client manager shutting down [ccount=" + _usermap.size() + "].");
// inform all of our clients that they are being shut down
for (Iterator iter = _usermap.values().iterator(); iter.hasNext(); ) {
@@ -124,17 +118,16 @@ public class ClientManager
try {
pc.shutdown();
} catch (Exception e) {
Log.warning("Client choked in shutdown() [client=" +
StringUtil.safeToString(pc) + "].");
Log.warning(
"Client choked in shutdown() [client=" + StringUtil.safeToString(pc) + "].");
Log.logStackTrace(e);
}
}
}
/**
* Configures the client manager with a factory for creating {@link
* PresentsClient} and {@link ClientResolver} classes for authenticated
* client connections.
* Configures the client manager with a factory for creating {@link PresentsClient} and {@link
* ClientResolver} classes for authenticated client connections.
*/
public void setClientFactory (ClientFactory factory)
{
@@ -182,8 +175,7 @@ public class ClientManager
}
/**
* Registers an observer that will be notified when clients start and end
* their sessions.
* Registers an observer that will be notified when clients start and end their sessions.
*/
public void addClientObserver (ClientObserver observer)
{
@@ -191,8 +183,7 @@ public class ClientManager
}
/**
* Removes an observer previously registered with {@link
* #addClientObserver}.
* Removes an observer previously registered with {@link #addClientObserver}.
*/
public void removeClientObserver (ClientObserver observer)
{
@@ -200,9 +191,8 @@ public class ClientManager
}
/**
* Returns the client instance that manages the client session for the
* specified authentication username or null if that client is not
* currently connected to the server.
* Returns the client instance that manages the client session for the specified authentication
* username or null if that client is not currently connected to the server.
*/
public PresentsClient getClient (Name authUsername)
{
@@ -210,9 +200,8 @@ public class ClientManager
}
/**
* 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).
* 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 ClientObject getClientObject (Name username)
{
@@ -220,8 +209,8 @@ public class ClientManager
}
/**
* Resolves the specified client, applies the supplied client
* operation to them and releases the client.
* Resolves the specified client, applies the supplied client operation to them and releases
* the client.
*/
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.
* 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). <em>Note:</em> this
* <b>must</b> be paired with a call to {@link #releaseClientObject}
* when the caller is finished with the client object.
* 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). <em>Note:</em> this <b>must</b> be paired with a call to
* {@link #releaseClientObject} when the caller is finished with the client object.
*/
public synchronized void resolveClientObject (
Name username, final ClientResolutionListener listener)
@@ -257,18 +244,16 @@ public class ClientManager
}
try {
// create a client resolver instance which will create our
// client object, populate it and notify the listeners
// create a client resolver instance which will create our client object, populate it
// and notify the listeners
clr = _factory.createClientResolver(username);
clr.init(username);
clr.addResolutionListener(this);
clr.addResolutionListener(listener);
_penders.put(username, clr);
// create and register our client object and give it back to the
// client resolver
clr.objectAvailable(
PresentsServer.omgr.registerObject(clr.createClientObject()));
// create and register our client object and give it back to the client resolver
clr.objectAvailable(PresentsServer.omgr.registerObject(clr.createClientObject()));
} catch (Exception e) {
// 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
* #resolveClientObject}. If this caller is the last reference, the
* object will be flushed and destroyed.
* Releases a client object that was obtained via a call to {@link #resolveClientObject}. If
* this caller is the last reference, the object will be flushed and destroyed.
*/
public void releaseClientObject (Name username)
{
ClientObject clobj = _objmap.get(username);
if (clobj == null) {
Log.warning("Requested to release unmapped client object " +
"[username=" + username + "].");
Log.warning("Requested to release unmapped client object [username=" + username + "].");
Thread.dumpStack();
return;
}
// decrement the reference count and stop here if there are
// remaining references
// decrement the reference count and stop here if there are remaining references
if (clobj.release()) {
return;
}
@@ -309,10 +291,10 @@ public class ClientManager
// documentation inherited from interface ClientResolutionListener
public synchronized void clientResolved (Name username, ClientObject clobj)
{
// because we added ourselves as a client resolution listener, the
// client object reference count was increased, but we're not a real
// resolver (who would have to call releaseClientObject() to release
// their reference), so we release our reference immediately
// because we added ourselves as a client resolution listener, the client object reference
// count was increased, but we're not a real resolver (who would have to call
// releaseClientObject() to release their reference), so we release our reference
// immediately
clobj.release();
// stuff the object into the mapping table
@@ -340,13 +322,11 @@ public class ClientManager
PresentsClient client = getClient(username);
if (client != null) {
Log.info("Resuming session [username=" + username +
", conn=" + conn + "].");
Log.info("Resuming session [username=" + username + ", conn=" + conn + "].");
client.resumeSession(req, conn);
} else {
Log.info("Session initiated [username=" + username +
", conn=" + conn + "].");
Log.info("Session initiated [username=" + username + ", conn=" + conn + "].");
// create a new client and stick'em in the table
client = _factory.createClient(req);
client.startSession(this, req, conn, rsp.authdata);
@@ -360,22 +340,20 @@ public class ClientManager
}
// documentation inherited
public synchronized void connectionFailed (
Connection conn, IOException fault)
public synchronized void connectionFailed (Connection conn, IOException fault)
{
// remove the client from the connection map
PresentsClient client = _conmap.remove(conn);
if (client != null) {
Log.info("Unmapped failed client [client=" + client +
", conn=" + conn + ", fault=" + fault + "].");
Log.info("Unmapped failed client [client=" + client + ", conn=" + conn +
", fault=" + fault + "].");
// let the client know the connection went away
client.wasUnmapped();
// and let the client know things went haywire
client.connectionFailed(fault);
} else if (!(conn instanceof AuthingConnection)) {
Log.info("Unmapped connection failed? [conn=" + conn +
", fault=" + fault + "].");
Log.info("Unmapped connection failed? [conn=" + conn + ", fault=" + fault + "].");
Thread.dumpStack();
}
}
@@ -386,8 +364,7 @@ public class ClientManager
// remove the client from the connection map
PresentsClient client = _conmap.remove(conn);
if (client != null) {
Log.debug("Unmapped client [client=" + client +
", conn=" + conn + "].");
Log.debug("Unmapped client [client=" + client + ", conn=" + conn + "].");
// let the client know the connection went away
client.wasUnmapped();
@@ -398,8 +375,7 @@ public class ClientManager
}
// documentation inherited from interface PresentsServer.Reporter
public void appendReport (
StringBuilder report, long now, long sinceLast, boolean reset)
public void appendReport (StringBuilder report, long now, long sinceLast, boolean reset)
{
report.append("* presents.ClientManager:\n");
report.append("- Sessions: ");
@@ -410,8 +386,8 @@ public class ClientManager
}
/**
* Called by the client instance when it has started its session. This is
* called from the dobjmgr thread.
* Called by the client instance when it has started its session. This is called from the
* dobjmgr thread.
*/
protected void clientSessionDidStart (final PresentsClient client)
{
@@ -425,15 +401,13 @@ public class ClientManager
}
/**
* Called by the client instance when the client requests a logoff.
* This is called from the conmgr thread.
* Called by the client instance when the client requests a logoff. This is called from the
* dobjmgr thread.
*/
protected synchronized void clientSessionDidEnd (
final PresentsClient client)
protected void clientSessionDidEnd (final PresentsClient client)
{
// remove the client from the username map
PresentsClient rc = _usermap.remove(
client.getCredentials().getUsername());
PresentsClient rc = _usermap.remove(client.getCredentials().getUsername());
// sanity check just because we can
if (rc == null) {
@@ -446,33 +420,26 @@ public class ClientManager
Log.info("Ending session " + client + ".");
}
// queue up a unit on the dobjmgr thread to notify the client observers
// that the session is ended
PresentsServer.omgr.postRunnable(new Runnable() {
public void run () {
_clobservers.apply(
new ObserverList.ObserverOp<ClientObserver>() {
public boolean apply (ClientObserver observer) {
observer.clientSessionDidEnd(client);
return true;
}
});
// notify the observers that the session is ended
_clobservers.apply(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
* disconnected too long and forcibly end their sessions.
* Called once per minute to check for clients that have been disconnected too long and
* forcibly end their sessions.
*/
protected void flushClients ()
{
ArrayList<PresentsClient> victims = null;
long now = System.currentTimeMillis();
// first build a list of our victims (we can't flush clients
// directly while iterating due to risk of a
// ConcurrentModificationException)
// first build a list of our victims (we can't flush clients directly while iterating due
// to risk of a ConcurrentModificationException)
for (PresentsClient client : _usermap.values()) {
if (client.checkExpired(now)) {
if (victims == null) {
@@ -485,13 +452,11 @@ public class ClientManager
if (victims != null) {
for (PresentsClient client : victims) {
try {
Log.info("Client expired, ending session " +
"[client=" + client +
", dtime=" + (now-client.getNetworkStamp()) + "ms].");
Log.info("Client expired, ending session [client=" + client +
", dtime=" + (now-client.getNetworkStamp()) + "ms].");
client.endSession();
} catch (Exception e) {
Log.warning("Choke while flushing client " +
"[victim=" + client + "].");
Log.warning("Choke while flushing client [victim=" + client + "].");
Log.logStackTrace(e);
}
}
@@ -502,20 +467,16 @@ public class ClientManager
protected class ClientOpResolver
implements ClientResolutionListener
{
public ClientOpResolver (ClientOp clop)
{
public ClientOpResolver (ClientOp clop) {
_clop = clop;
}
// documentation inherited from interface
public void clientResolved (Name username, ClientObject clobj)
{
public void clientResolved (Name username, ClientObject clobj) {
try {
_clop.apply(clobj);
} catch (Exception e) {
Log.warning("Client op failed [username=" + username +
", clop=" + _clop + "].");
Log.warning("Client op failed [username=" + username + ", clop=" + _clop + "].");
Log.logStackTrace(e);
} 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);
}
@@ -533,20 +492,16 @@ public class ClientManager
}
/** A mapping from auth username to client instances. */
protected HashMap<Name,PresentsClient> _usermap =
new HashMap<Name,PresentsClient>();
protected HashMap<Name,PresentsClient> _usermap = new HashMap<Name,PresentsClient>();
/** A mapping from connections to client instances. */
protected HashMap<Connection,PresentsClient> _conmap =
new HashMap<Connection,PresentsClient>();
protected HashMap<Connection,PresentsClient> _conmap = new HashMap<Connection,PresentsClient>();
/** A mapping from usernames to client object instances. */
protected HashMap<Name,ClientObject> _objmap =
new HashMap<Name,ClientObject>();
protected HashMap<Name,ClientObject> _objmap = new HashMap<Name,ClientObject>();
/** A mapping of pending client resolvers. */
protected HashMap<Name,ClientResolver> _penders =
new HashMap<Name,ClientResolver>();
protected HashMap<Name,ClientResolver> _penders = new HashMap<Name,ClientResolver>();
/** The client class in use. */
protected ClientFactory _factory = ClientFactory.DEFAULT;