Created ServerCommunicator which does all I/O using the ConnectionManager's

polled socket system. This much more cleanly and efficiently integrates peer
(and other server to server) connections into the normal server I/O framework
and should also eliminate for good the annoying server hangs that result when
our old blocking client I/O threads got their pants wedged.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@5510 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2008-11-07 00:27:07 +00:00
parent b81ccde75b
commit 89f02fc728
16 changed files with 777 additions and 594 deletions
@@ -183,17 +183,10 @@ public class BlockingCommunicator extends Communicator
return _lastWrite;
}
/**
* Callback called by the reader when the authentication process completes successfully. Here
* we extract the bootstrap information for the client and start up the writer thread to manage
* the other half of our bi-directional message stream.
*/
@Override // from Communicator
protected synchronized void logonSucceeded (AuthResponseData data)
{
log.debug("Logon succeeded: " + data);
// create our distributed object manager
_omgr = new ClientDObjectMgr(this, _client);
super.logonSucceeded(data);
// create a new writer thread and start it up
if (_writer != null) {
@@ -201,19 +194,13 @@ public class BlockingCommunicator extends Communicator
}
_writer = new Writer();
_writer.start();
// fill the auth data into the client's local field so that it can be requested by external
// entities
_client._authData = data;
// wait for the bootstrap notification before we claim that we're actually logged on
}
/**
* Callback called by the reader or writer thread when something goes awry with our socket
* connection to the server.
*/
protected synchronized void connectionFailed (IOException ioe)
protected synchronized void connectionFailed (final IOException ioe)
{
// make sure the socket isn't already closed down (meaning we've already dealt with the
// failed connection)
@@ -224,7 +211,11 @@ public class BlockingCommunicator extends Communicator
log.info("Connection failed", ioe);
// let the client know that things went south
_client.notifyObservers(Client.CLIENT_CONNECTION_FAILED, ioe);
notifyClientObservers(new ObserverOps.Client() {
protected void notify (ClientObserver obs) {
obs.clientConnectionFailed(_client, ioe);
}
});
// and request that we go through the motions of logging off
logoff();
@@ -260,7 +251,7 @@ public class BlockingCommunicator extends Communicator
closeChannel();
// let the client know when we finally go away
_client.cleanup(_logonError);
clientCleanup(_logonError);
}
log.debug("Reader thread exited.");
@@ -276,7 +267,11 @@ public class BlockingCommunicator extends Communicator
log.debug("Writer thread exited.");
// let the client observers know that we're logged off
_client.notifyObservers(Client.CLIENT_DID_LOGOFF, null);
notifyClientObservers(new ObserverOps.Session() {
protected void notify (SessionObserver obs) {
obs.clientDidLogoff(_client);
}
});
// now that the writer thread has gone away, we can safely close our socket and let the
// client know that the logoff process has completed
@@ -284,7 +279,7 @@ public class BlockingCommunicator extends Communicator
// let the client know when we finally go away
if (_reader == null) {
_client.cleanup(_logonError);
clientCleanup(_logonError);
}
}
@@ -439,14 +434,6 @@ public class BlockingCommunicator extends Communicator
_datagramChannel.write(buf);
}
/**
* Makes a note of the time at which we last communicated with the server.
*/
protected synchronized void updateWriteStamp ()
{
_lastWrite = System.currentTimeMillis();
}
/**
* Reads a new message from the socket (blocking until a message has arrived).
*/
@@ -504,16 +491,6 @@ public class BlockingCommunicator extends Communicator
}
}
/**
* Callback called by the reader thread when it has parsed a new message from the socket and
* wishes to have it processed.
*/
protected void processMessage (DownstreamMessage msg)
{
// post this message to the dobjmgr queue
_omgr.processMessage(msg);
}
protected void openChannel (InetAddress host)
throws IOException
{
@@ -545,13 +522,17 @@ public class BlockingCommunicator extends Communicator
@Override
protected void willStart ()
{
// first we connect and authenticate with the server
try {
// connect to the server
connect();
// then authenticate
logon();
// construct an auth request and send it
sendMessage(new AuthRequest(_client.getCredentials(), _client.getVersion(),
_client.getBootGroups()));
// now wait for the auth response
log.debug("Waiting for auth response.");
gotAuthResponse((AuthResponse)receiveMessage());
} catch (Exception e) {
log.debug("Logon failed: " + e);
@@ -586,30 +567,6 @@ public class BlockingCommunicator extends Communicator
_oout = new ObjectOutputStream(_fout);
}
protected void logon ()
throws IOException, LogonException
{
// construct an auth request and send it
AuthRequest req = new AuthRequest(
_client.getCredentials(), _client.getVersion(), _client.getBootGroups());
sendMessage(req);
// now wait for the auth response
log.debug("Waiting for auth response.");
AuthResponse rsp = (AuthResponse)receiveMessage();
AuthResponseData data = rsp.getData();
log.debug("Got auth response: " + data);
// if the auth request failed, we want to let the communicator know by throwing a logon
// exception
if (!data.code.equals(AuthResponseData.SUCCESS)) {
throw new LogonException(data.code);
}
// we're all clear. let the communicator know that we're in
logonSucceeded(data);
}
// now that we're authenticated, we manage the reading half of things by continuously
// reading messages from the socket and processing them
@Override
@@ -961,7 +918,6 @@ public class BlockingCommunicator extends Communicator
protected DatagramChannel _datagramChannel;
protected Queue<UpstreamMessage> _dataq = new Queue<UpstreamMessage>();
protected long _lastWrite;
protected Exception _logonError;
/** We use this to frame our upstream messages. */
@@ -983,7 +939,6 @@ public class BlockingCommunicator extends Communicator
protected DatagramSequencer _sequencer;
protected ClientDObjectMgr _omgr;
protected ClassLoader _loader;
/** The number of times per port to try to establish a datagram "connection". */
@@ -418,8 +418,12 @@ public class Client
return false;
}
// notify our observers
notifyObservers(CLIENT_WILL_LOGON, null);
// notify our observers immediately
_observers.apply(new ObserverOps.Session() {
protected void notify (SessionObserver obs) {
obs.clientWillLogon(Client.this);
}
});
// otherwise create a new communicator instance and start it up. this will initiate the
// logon process
@@ -504,7 +508,15 @@ public class Client
}
// if the request is abortable, let's run it past the observers before we act upon it
if (abortable && notifyObservers(CLIENT_WILL_LOGOFF, null)) {
final boolean[] rejected = new boolean[] { false };
_observers.apply(new ObserverOps.Client() {
protected void notify (ClientObserver obs) {
if (!obs.clientWillLogoff(Client.this)) {
rejected[0] = true;
}
}
});
if (abortable && rejected[0]) {
return false;
}
@@ -527,7 +539,12 @@ public class Client
public String[] prepareStandaloneLogon ()
{
_standalone = true;
notifyObservers(CLIENT_WILL_LOGON, null);
// notify our observers immediately
_observers.apply(new ObserverOps.Session() {
protected void notify (SessionObserver obs) {
obs.clientWillLogon(Client.this);
}
});
return getBootGroups();
}
@@ -548,7 +565,11 @@ public class Client
*/
public void standaloneLogoff ()
{
notifyObservers(CLIENT_DID_LOGOFF, null);
notifyObservers(new ObserverOps.Session() {
protected void notify (SessionObserver obs) {
obs.clientDidLogoff(Client.this);
}
});
cleanup(null); // this will set _standalone to false
}
@@ -715,9 +736,9 @@ public class Client
*/
protected void reportLogonTribulations (final LogonException cause)
{
_runQueue.postRunnable(new Runnable() {
public void run () {
notifyObservers(CLIENT_FAILED_TO_LOGON, cause);
notifyObservers(new ObserverOps.Client() {
protected void notify (ClientObserver obs) {
obs.clientFailedToLogon(Client.this, cause);
}
});
}
@@ -732,16 +753,24 @@ public class Client
_clobj = clobj;
// let the client know that logon has now fully succeeded
notifyObservers(CLIENT_DID_LOGON, null);
notifyObservers(new ObserverOps.Session() {
protected void notify (SessionObserver obs) {
obs.clientDidLogon(Client.this);
}
});
}
/**
* Called by the invocation director if it fails to subscribe to the client object after logon.
*/
protected void getClientObjectFailed (Exception cause)
protected void getClientObjectFailed (final Exception cause)
{
// pass the buck onto the listeners
notifyObservers(CLIENT_FAILED_TO_LOGON, cause);
notifyObservers(new ObserverOps.Client() {
protected void notify (ClientObserver obs) {
obs.clientFailedToLogon(Client.this, cause);
}
});
}
/**
@@ -753,32 +782,23 @@ public class Client
_cloid = _clobj.getOid();
// report to our observers
notifyObservers(CLIENT_OBJECT_CHANGED, null);
notifyObservers(new ObserverOps.Session() {
protected void notify (SessionObserver obs) {
obs.clientObjectDidChange(Client.this);
}
});
}
protected boolean notifyObservers (int code, Exception cause)
protected void notifyObservers (final ObserverOps.Session op)
{
final Notifier noty = new Notifier(code, cause);
Runnable unit = new Runnable() {
public void run () {
synchronized (_observers) {
_observers.apply(noty);
}
}
};
// we need to run immediately if this is WILL_LOGON, WILL_LOGOFF or if we have no RunQueue
// (which currently only happens in some really obscure circumstances where we're using a
// Client instance on the server so that we can sort of pretend to be a real client)
if (code == CLIENT_WILL_LOGON || code == CLIENT_WILL_LOGOFF || _runQueue == null) {
unit.run();
return noty.getRejected();
if (_runQueue == null) {
_observers.apply(op);
} else {
// otherwise we can queue this notification up with our RunQueue and ensure that it's
// run on the proper thread
_runQueue.postRunnable(unit);
return false;
_runQueue.postRunnable(new Runnable() {
public void run () {
_observers.apply(op);
}
});
}
}
@@ -809,11 +829,15 @@ public class Client
// now that the communicator is cleaned up; this allows a logon failure listener to
// immediately try another logon (hopefully with something changed like the server
// or port)
if (logonError != null) {
notifyObservers(CLIENT_FAILED_TO_LOGON, logonError);
} else {
notifyObservers(CLIENT_DID_CLEAR, null);
}
notifyObservers(new ObserverOps.Client() {
protected void notify (ClientObserver obs) {
if (logonError != null) {
obs.clientFailedToLogon(Client.this, logonError);
} else {
obs.clientDidClear(Client.this);
}
}
});
}
});
}
@@ -844,81 +868,6 @@ public class Client
return false;
}
/**
* Used to notify client observers of events.
*/
protected class Notifier
implements ObserverList.ObserverOp<SessionObserver>
{
public Notifier (int code, Exception cause)
{
_code = code;
_cause = cause;
_rejected = false;
}
public boolean getRejected ()
{
return _rejected;
}
public boolean apply (SessionObserver obs)
{
switch (_code) {
case CLIENT_WILL_LOGON:
obs.clientWillLogon(Client.this);
break;
case CLIENT_DID_LOGON:
obs.clientDidLogon(Client.this);
break;
case CLIENT_FAILED_TO_LOGON:
if (obs instanceof ClientObserver) {
((ClientObserver)obs).clientFailedToLogon(Client.this, _cause);
}
break;
case CLIENT_OBJECT_CHANGED:
obs.clientObjectDidChange(Client.this);
break;
case CLIENT_CONNECTION_FAILED:
if (obs instanceof ClientObserver) {
((ClientObserver)obs).clientConnectionFailed(Client.this, _cause);
}
break;
case CLIENT_WILL_LOGOFF:
if (obs instanceof ClientObserver) {
if (!((ClientObserver)obs).clientWillLogoff(Client.this)) {
_rejected = true;
}
}
break;
case CLIENT_DID_LOGOFF:
obs.clientDidLogoff(Client.this);
break;
case CLIENT_DID_CLEAR:
if (obs instanceof ClientObserver) {
((ClientObserver)obs).clientDidClear(Client.this);
}
break;
default:
throw new RuntimeException("Invalid code supplied to notifyObservers: " + _code);
}
return true;
}
protected int _code;
protected Exception _cause;
protected boolean _rejected;
}
/** Handles the process of switching between servers.
* See {@link Client#moveToServer(String,int[],int[],ConfirmListener)}. */
protected class ServerSwitcher extends ClientAdapter
@@ -1037,8 +986,7 @@ public class Client
protected int[] _datagramPorts;
/** Our list of client observers. */
protected ObserverList<SessionObserver> _observers =
new ObserverList<SessionObserver>(ObserverList.SAFE_IN_ORDER_NOTIFY);
protected ObserverList<SessionObserver> _observers = ObserverList.newSafeInOrder();
/** The entity that manages our network communications. */
protected Communicator _comm;
@@ -1075,14 +1023,4 @@ public class Client
/** How often we recompute our time offset from the server. */
protected static final long CLOCK_SYNC_INTERVAL = 600 * 1000L;
// client observer codes
static final int CLIENT_WILL_LOGON = 0;
static final int CLIENT_DID_LOGON = 1;
static final int CLIENT_FAILED_TO_LOGON = 2;
static final int CLIENT_OBJECT_CHANGED = 3;
static final int CLIENT_CONNECTION_FAILED = 4;
static final int CLIENT_WILL_LOGOFF = 5;
static final int CLIENT_DID_LOGOFF = 6;
static final int CLIENT_DID_CLEAR = 7;
}
@@ -44,10 +44,10 @@ import com.threerings.presents.dobj.ObjectDestroyedEvent;
import com.threerings.presents.dobj.Subscriber;
import com.threerings.presents.net.BootstrapData;
import com.threerings.presents.net.BootstrapNotification;
import com.threerings.presents.net.DownstreamMessage;
import com.threerings.presents.net.EventNotification;
import com.threerings.presents.net.FailureResponse;
import com.threerings.presents.net.ForwardEventRequest;
import com.threerings.presents.net.Message;
import com.threerings.presents.net.ObjectResponse;
import com.threerings.presents.net.PongResponse;
import com.threerings.presents.net.SubscribeRequest;
@@ -157,10 +157,10 @@ public class ClientDObjectMgr
}
/**
* Called by the communicator when a downstream message arrives from the network layer. We
* queue it up for processing and request some processing time on the main thread.
* Called by the communicator when a message arrives from the network layer. We queue it up for
* processing and request some processing time on the main thread.
*/
public void processMessage (DownstreamMessage msg)
public void processMessage (Message msg)
{
// append it to our queue
_actions.append(msg);
@@ -211,6 +211,9 @@ public class ClientDObjectMgr
} else {
doUnsubscribe(act.oid, act.target);
}
} else {
log.warning("Unknown action", "action", obj);
}
}
}
@@ -21,6 +21,9 @@
package com.threerings.presents.client;
import com.threerings.presents.net.AuthResponse;
import com.threerings.presents.net.AuthResponseData;
import com.threerings.presents.net.Message;
import com.threerings.presents.net.UpstreamMessage;
/**
@@ -66,7 +69,70 @@ public abstract class Communicator
/**
* Returns the time at which we last sent a packet to the server.
*/
public abstract long getLastWrite ();
public long getLastWrite ()
{
return _lastWrite;
}
/**
* Makes a note of the time at which we last communicated with the server.
*/
protected synchronized void updateWriteStamp ()
{
_lastWrite = System.currentTimeMillis();
}
/**
* Subclasses must call this method when they receive the authentication response.
*/
protected void gotAuthResponse (AuthResponse rsp)
throws LogonException
{
AuthResponseData data = rsp.getData();
if (!data.code.equals(AuthResponseData.SUCCESS)) {
throw new LogonException(data.code);
}
logonSucceeded(data);
}
/**
* Called when the authentication process completes successfully. Derived classes can override
* this method and complete any initialization that need wait for authentication success.
*/
protected synchronized void logonSucceeded (AuthResponseData data)
{
// create our distributed object manager
_omgr = new ClientDObjectMgr(this, _client);
// fill the auth data into the client's local field so that it can be requested by external
// entities
_client._authData = data;
// wait for the bootstrap notification before we claim that we're actually logged on
}
/**
* Callback called by the reader thread when it has parsed a new message from the socket and
* wishes to have it processed.
*/
protected void processMessage (Message msg)
{
// post this message to the dobjmgr queue
_omgr.processMessage(msg);
}
protected void notifyClientObservers (ObserverOps.Session op)
{
_client.notifyObservers(op);
}
protected void clientCleanup (Exception logonError)
{
_client.cleanup(logonError);
_client = null; // prevent any post-cleanup tomfoolery
}
protected Client _client;
protected ClientDObjectMgr _omgr;
protected long _lastWrite;
}
@@ -0,0 +1,49 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2008 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.presents.client;
import com.samskivert.util.ObserverList;
/**
* Used to notify session and client observers.
*/
public class ObserverOps
{
public abstract static class Session implements ObserverList.ObserverOp<SessionObserver>
{
public boolean apply (SessionObserver obs) {
notify(obs);
return true;
}
protected abstract void notify (SessionObserver obs);
}
public abstract static class Client extends Session
{
@Override public void notify (SessionObserver obs) {
if (obs instanceof ClientObserver) {
notify((ClientObserver)obs);
}
}
protected abstract void notify (ClientObserver obs);
}
}