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:
@@ -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;
|
||||
|
||||
@@ -346,7 +346,7 @@ public class PresentsClient
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
// from interface ClientResolutionListener
|
||||
public void clientResolved (Name username, ClientObject clobj)
|
||||
{
|
||||
// we'll be keeping this bad boy
|
||||
@@ -360,7 +360,7 @@ public class PresentsClient
|
||||
_cmgr.clientSessionDidStart(this);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
// from interface ClientResolutionListener
|
||||
public void resolutionFailed (Name username, Exception reason)
|
||||
{
|
||||
// urk; nothing to do but complain and get the f**k out of dodge
|
||||
@@ -371,7 +371,7 @@ public class PresentsClient
|
||||
endSession();
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
// from interface MessageHandler
|
||||
public void handleMessage (UpstreamMessage message)
|
||||
{
|
||||
_messagesIn++; // count 'em up!
|
||||
@@ -403,7 +403,7 @@ public class PresentsClient
|
||||
disp.dispatch(this, message);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
// from interface ProxySubscriber
|
||||
public void objectAvailable (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)
|
||||
{
|
||||
postMessage(new FailureResponse(oid));
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
// from interface ProxySubscriber
|
||||
public void eventReceived (DEvent event)
|
||||
{
|
||||
if (event instanceof PresentsDObjectMgr.AccessObjectEvent) {
|
||||
|
||||
Reference in New Issue
Block a user