Implemented a Ray-proposed solution where we keep track of events that were

(pre-applied and) posted before we received our object but processed (and hence
dispatched to us) after we received our object.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4715 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2007-05-18 03:37:14 +00:00
parent db3fe80e9d
commit 9bcca099ac
10 changed files with 268 additions and 230 deletions
@@ -76,7 +76,7 @@ import com.threerings.presents.server.net.MessageHandler;
* from the conmgr thread and therefore also need not be synchronized.
*/
public class PresentsClient
implements ProxySubscriber, MessageHandler, ClientResolutionListener
implements MessageHandler, ClientResolutionListener
{
/** Used by {@link #setUsername} to report success or failure. */
public static interface UserChangeListener
@@ -321,31 +321,6 @@ public class PresentsClient
}
}
/**
* Makes a note that this client is subscribed to this object so that we can clean up after
* ourselves if and when the client goes away. This is called by the client internals and
* needn't be called by code outside the client.
*/
public synchronized void mapSubscrip (DObject object)
{
_subscrips.put(object.getOid(), object);
}
/**
* Makes a note that this client is no longer subscribed to this object. The subscription map
* is used to clean up after the client when it goes away. This is called by the client
* internals and needn't be called by code outside the client.
*/
public synchronized void unmapSubscrip (int oid)
{
DObject object = _subscrips.remove(oid);
if (object != null) {
object.removeSubscriber(this);
} else {
Log.warning("Requested to unmap non-existent subscription [oid=" + oid + "].");
}
}
// from interface ClientResolutionListener
public void clientResolved (Name username, ClientObject clobj)
{
@@ -403,35 +378,6 @@ public class PresentsClient
disp.dispatch(this, message);
}
// from interface ProxySubscriber
public void objectAvailable (DObject object)
{
if (postMessage(new ObjectResponse<DObject>(object))) {
// make a note of this new subscription
mapSubscrip(object);
} else {
// if we failed to send the object response, unsubscribe
object.removeSubscriber(this);
}
}
// from interface ProxySubscriber
public void requestFailed (int oid, ObjectAccessException cause)
{
postMessage(new FailureResponse(oid));
}
// from interface ProxySubscriber
public void eventReceived (DEvent event)
{
if (event instanceof PresentsDObjectMgr.AccessObjectEvent) {
Log.warning("Ignoring event that shouldn't be forwarded " + event + ".");
Thread.dumpStack();
} else {
postMessage(new EventNotification(event));
}
}
/**
* Called when {@link #setUsername} has been called and the new client object is about to be
* applied to this client. The old client object will not yet have been destroyed, so any final
@@ -556,18 +502,35 @@ public class PresentsClient
});
}
/**
* Makes a note that this client is no longer subscribed to this object. The subscription map
* is used to clean up after the client when it goes away.
*/
protected synchronized void unmapSubscrip (int oid)
{
ClientProxy rec;
synchronized (_subscrips) {
rec = _subscrips.remove(oid);
}
if (rec != null) {
rec.unsubscribe();
} else {
Log.warning("Requested to unmap non-existent subscription [oid=" + oid + "].");
}
}
/**
* Clears out the tracked client subscriptions. Called when the client goes away and shouldn't
* be called otherwise.
*/
protected void clearSubscrips (boolean verbose)
{
for (DObject object : _subscrips.values()) {
for (ClientProxy rec : _subscrips.values()) {
if (verbose) {
Log.info("Clearing subscription [client=" + this +
", obj=" + object.getOid() + "].");
", obj=" + rec.object.getOid() + "].");
}
object.removeSubscriber(this);
rec.unsubscribe();
}
_subscrips.clear();
}
@@ -615,6 +578,20 @@ public class PresentsClient
clearSubscrips(false);
}
/**
* Called to inform derived classes when the client has subscribed to a distributed object.
*/
protected void subscribedToObject (DObject object)
{
}
/**
* Called to inform derived classes when the client has unsubscribed from a distributed object.
*/
protected void unsubscribedFromObject (DObject object)
{
}
/**
* This is called once we have a handle on the client distributed object. It sends a bootstrap
* notification to the client with all the information it will need to interact with the
@@ -803,6 +780,72 @@ public class PresentsClient
buf.append(", out=").append(_messagesOut);
}
/**
* Creates a properly initialized inner-class proxy subscriber.
*/
protected ClientProxy createProxySubscriber ()
{
return new ClientProxy();
}
/** Used to track information about an object subscription. */
protected class ClientProxy implements ProxySubscriber
{
public DObject object;
public void unsubscribe ()
{
object.removeSubscriber(this);
unsubscribedFromObject(object);
}
// from interface ProxySubscriber
public void objectAvailable (DObject object)
{
if (postMessage(new ObjectResponse<DObject>(object))) {
_firstEventId = PresentsServer.omgr.getNextEventId(false);
this.object = object;
synchronized (_subscrips) {
// make a note of this new subscription
_subscrips.put(object.getOid(), this);
}
subscribedToObject(object);
} else {
// if we failed to send the object response, unsubscribe
object.removeSubscriber(this);
}
}
// from interface ProxySubscriber
public void requestFailed (int oid, ObjectAccessException cause)
{
postMessage(new FailureResponse(oid));
}
// from interface ProxySubscriber
public void eventReceived (DEvent event)
{
if (event instanceof PresentsDObjectMgr.AccessObjectEvent) {
Log.warning("Ignoring event that shouldn't be forwarded " + event + ".");
Thread.dumpStack();
return;
}
// if this message was posted before we received this object and has already been
// applied, then this event has already been applied to this object and we should not
// forward it, it is equivalent to all the events applied to the object before we
// became a subscriber
if (event.eventId < _firstEventId) {
return;
}
postMessage(new EventNotification(event));
}
protected long _firstEventId;
}
/**
* Message dispatchers are used to dispatch each different type of upstream message. We can
* look the dispatcher up in a table and invoke it through an overloaded member which is faster
@@ -827,7 +870,7 @@ public class PresentsClient
// Log.info("Subscribing [client=" + client + ", oid=" + req.getOid() + "].");
// forward the subscribe request to the omgr for processing
PresentsServer.omgr.subscribeToObject(req.getOid(), client);
PresentsServer.omgr.subscribeToObject(req.getOid(), client.createProxySubscriber());
}
}
@@ -842,9 +885,7 @@ public class PresentsClient
int oid = req.getOid();
// Log.info("Unsubscribing " + client + " [oid=" + oid + "].");
// forward the unsubscribe request to the omgr for processing
PresentsServer.omgr.unsubscribeFromObject(oid, client);
// update our subscription tracking table
// unsubscribe from the object and clear out our proxy
client.unmapSubscrip(oid);
// post a response to the client letting them know that we will no longer send them
@@ -904,7 +945,7 @@ public class PresentsClient
protected Name _username;
protected Connection _conn;
protected ClientObject _clobj;
protected HashIntMap<DObject> _subscrips = new HashIntMap<DObject>();
protected HashIntMap<ClientProxy> _subscrips = new HashIntMap<ClientProxy>();
protected ClassLoader _loader;
/** The time at which this client started their session. */
@@ -93,6 +93,17 @@ public class PresentsDObjectMgr
PresentsServer.registerReporter(this);
}
/**
* Returns the id to be assigned to the next event posted to the event queue.
*
* @param increment if true, the event id will be incremented so that the caller can "claim"
* the returned event id.
*/
public synchronized long getNextEventId (boolean increment)
{
return increment ? _nextEventId++ : _nextEventId;
}
/**
* Sets up an access controller that will be provided to any distributed objects created on the
* server. The controllers can subsequently be overridden if desired, but a default controller
@@ -169,7 +180,8 @@ public class PresentsDObjectMgr
// from interface DObjectManager
public void postEvent (DEvent event)
{
// just append it to the queue
// assign the event's id and append it to the queue
event.eventId = getNextEventId(true);
_evqueue.append(event);
}
@@ -973,6 +985,9 @@ public class PresentsDObjectMgr
/** Used during unit profiling for timing values. */
protected Perf _timer = Perf.getPerf();
/** A monotonically increasing counter used to assign an id to all dispatched events. */
protected long _nextEventId = 1;
/** Used to profile our events and runnable units. */
protected HashMap<String,UnitProfile> _profiles = new HashMap<String,UnitProfile>();