diff --git a/src/java/com/threerings/presents/client/Client.java b/src/java/com/threerings/presents/client/Client.java index 629936c6d..67984916c 100644 --- a/src/java/com/threerings/presents/client/Client.java +++ b/src/java/com/threerings/presents/client/Client.java @@ -1,5 +1,5 @@ // -// $Id: Client.java,v 1.32 2002/10/21 18:04:56 mdb Exp $ +// $Id: Client.java,v 1.33 2002/10/29 23:51:26 mdb Exp $ package com.threerings.presents.client; @@ -224,8 +224,9 @@ public class Client { InvocationService isvc = getService(sclass); if (isvc == null) { - throw new RuntimeException(sclass.getName() + " isn't available. " + - "I can't bear to go on."); + throw new RuntimeException( + sclass.getName() + " isn't available. " + + "I can't bear to go on."); } return isvc; } diff --git a/src/java/com/threerings/presents/client/Communicator.java b/src/java/com/threerings/presents/client/Communicator.java index 4b232022e..cad8b0b2f 100644 --- a/src/java/com/threerings/presents/client/Communicator.java +++ b/src/java/com/threerings/presents/client/Communicator.java @@ -1,5 +1,5 @@ // -// $Id: Communicator.java,v 1.21 2002/07/23 05:52:48 mdb Exp $ +// $Id: Communicator.java,v 1.22 2002/10/29 23:51:26 mdb Exp $ package com.threerings.presents.client; @@ -13,6 +13,8 @@ import java.net.InetAddress; import java.net.Socket; import com.samskivert.io.NestableIOException; +import com.samskivert.util.Interval; +import com.samskivert.util.IntervalManager; import com.samskivert.util.LoopingThread; import com.samskivert.util.Queue; @@ -84,6 +86,16 @@ public class Communicator // start up the writer thread if everything went successfully _reader = new Reader(); _reader.start(); + + // register an interval to send pings when appropriate + _piid = IntervalManager.register(new Interval() { + public void intervalExpired (int id, Object arg) { + if (checkNeedsPing()) { + Log.info("Upstream idle, sending ping."); + postMessage(new PingRequest()); + } + } + }, 5000L, null, true); } /** @@ -99,6 +111,11 @@ public class Communicator return; } + // stop our ping interval + if (_piid != -1) { + IntervalManager.remove(_piid); + } + // post a logoff message postMessage(new LogoffRequest()); @@ -263,6 +280,28 @@ public class Communicator // then write the framed message to actual output stream _fout.writeFrameAndReset(_out); + + // make a note of our most recent write time + updateWriteStamp(); + } + + /** + * Makes a note of the time at which we last communicated with the + * server. + */ + protected synchronized void updateWriteStamp () + { + _lastWrite = System.currentTimeMillis(); + } + + /** + * Checks to see if we need to send a ping to the server because we + * haven't communicated in sufficiently long. + */ + protected synchronized boolean checkNeedsPing () + { + long now = System.currentTimeMillis(); + return (now - _lastWrite > PingRequest.PING_INTERVAL); } /** @@ -507,6 +546,9 @@ public class Communicator protected OutputStream _out; protected Queue _msgq = new Queue(); + protected int _piid; + protected long _lastWrite; + /** We use this to frame our upstream messages. */ protected FramingOutputStream _fout; protected ObjectOutputStream _oout; diff --git a/src/java/com/threerings/presents/net/PingRequest.java b/src/java/com/threerings/presents/net/PingRequest.java index 23d059fbe..d478daddd 100644 --- a/src/java/com/threerings/presents/net/PingRequest.java +++ b/src/java/com/threerings/presents/net/PingRequest.java @@ -1,5 +1,5 @@ // -// $Id: PingRequest.java,v 1.7 2002/07/23 05:52:48 mdb Exp $ +// $Id: PingRequest.java,v 1.8 2002/10/29 23:51:26 mdb Exp $ package com.threerings.presents.net; @@ -10,6 +10,11 @@ import com.threerings.io.ObjectOutputStream; public class PingRequest extends UpstreamMessage { + /** The number of milliseconds of idle upstream that are allowed to + * elapse before the client sends a ping message to the server to let + * it know that we're still alive. */ + public static final long PING_INTERVAL = 60 * 1000L; + /** * Zero argument constructor used when unserializing an instance. */ diff --git a/src/java/com/threerings/presents/server/net/AuthingConnection.java b/src/java/com/threerings/presents/server/net/AuthingConnection.java index ba031de41..1ba547dae 100644 --- a/src/java/com/threerings/presents/server/net/AuthingConnection.java +++ b/src/java/com/threerings/presents/server/net/AuthingConnection.java @@ -1,5 +1,5 @@ // -// $Id: AuthingConnection.java,v 1.7 2002/09/24 00:50:27 mdb Exp $ +// $Id: AuthingConnection.java,v 1.8 2002/10/29 23:51:26 mdb Exp $ package com.threerings.presents.server.net; @@ -16,8 +16,8 @@ import com.threerings.presents.net.UpstreamMessage; * The authing connection manages the client connection until * authentication has completed (for better or for worse). */ -public class AuthingConnection - extends Connection implements MessageHandler +public class AuthingConnection extends Connection + implements MessageHandler { /** * Creates a new authing connection object that will manage the @@ -27,7 +27,8 @@ public class AuthingConnection NonblockingSocket socket) throws IOException { - super(cmgr, socket); + super(cmgr, socket, System.currentTimeMillis()); + // we are our own message handler setMessageHandler(this); } diff --git a/src/java/com/threerings/presents/server/net/Connection.java b/src/java/com/threerings/presents/server/net/Connection.java index 64a42f4ce..2e8a37bdd 100644 --- a/src/java/com/threerings/presents/server/net/Connection.java +++ b/src/java/com/threerings/presents/server/net/Connection.java @@ -1,5 +1,5 @@ // -// $Id: Connection.java,v 1.9 2002/10/26 02:37:59 shaper Exp $ +// $Id: Connection.java,v 1.10 2002/10/29 23:51:26 mdb Exp $ package com.threerings.presents.server.net; @@ -7,6 +7,7 @@ import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.SocketTimeoutException; import ninja2.core.io_core.nbio.*; @@ -19,6 +20,7 @@ import com.threerings.io.ObjectOutputStream; import com.threerings.presents.Log; import com.threerings.presents.net.DownstreamMessage; +import com.threerings.presents.net.PingRequest; import com.threerings.presents.net.UpstreamMessage; /** @@ -36,14 +38,15 @@ public abstract class Connection implements NetEventHandler * @param cmgr The connection manager with which this connection is * associated. * @param socket The socket from which we'll be reading messages. - * @param fout The framing output stream via which we'll write - * serialized messages to the client. + * @param createStamp The time at which this connection was created. */ - public Connection (ConnectionManager cmgr, NonblockingSocket socket) + public Connection (ConnectionManager cmgr, NonblockingSocket socket, + long createStamp) throws IOException { _cmgr = cmgr; _socket = socket; + _lastEvent = createStamp; // create a select item that will allow us to be incorporated into // the main event polling loop @@ -210,8 +213,11 @@ public abstract class Connection implements NetEventHandler /** * Called when our client socket has data available for reading. */ - public void handleEvent (Selectable source, short events) + public void handleEvent (long when, Selectable source, short events) { + // make a note that we received an event as of this time + _lastEvent = when; + try { // we're lazy about creating our input streams because we may // be inheriting them from our authing connection and we don't @@ -248,6 +254,21 @@ public abstract class Connection implements NetEventHandler } } + // documentation inherited from interface + public void checkIdle (long now) + { + long idleMillis = now - _lastEvent; + if (idleMillis < PingRequest.PING_INTERVAL + LATENCY_GRACE) { + return; + } + if (isClosed()) { + return; + } + Log.info("Disconncecting idle client [conn=" + this + + ", idle=" + idleMillis + "ms]."); + handleFailure(new SocketTimeoutException("Idle too long")); + } + /** * Posts a downstream message for delivery to this connection. The * message will be delivered by the conmgr thread as soon as it gets @@ -263,6 +284,8 @@ public abstract class Connection implements NetEventHandler protected NonblockingSocket _socket; protected SelectItem _selitem; + protected long _lastEvent; + protected InputStream _in; protected FramedInputStream _fin; protected ObjectInputStream _oin; @@ -271,4 +294,9 @@ public abstract class Connection implements NetEventHandler protected ObjectOutputStream _oout; protected MessageHandler _handler; + + /** The number of milliseconds beyond the ping interval that we allow + * a client's network connection to be idle before we forcibly + * disconnect them. */ + protected static final long LATENCY_GRACE = 30 * 1000L; } diff --git a/src/java/com/threerings/presents/server/net/ConnectionManager.java b/src/java/com/threerings/presents/server/net/ConnectionManager.java index 73c8e6442..86dd9d35c 100644 --- a/src/java/com/threerings/presents/server/net/ConnectionManager.java +++ b/src/java/com/threerings/presents/server/net/ConnectionManager.java @@ -1,5 +1,5 @@ // -// $Id: ConnectionManager.java,v 1.22 2002/10/21 20:56:21 mdb Exp $ +// $Id: ConnectionManager.java,v 1.23 2002/10/29 23:51:26 mdb Exp $ package com.threerings.presents.server.net; @@ -54,9 +54,13 @@ public class ConnectionManager extends LoopingThread _litem = new SelectItem(_listener, Selectable.ACCEPT_READY); // when an ACCEPT_READY event happens, we do this: _litem.obj = new NetEventHandler() { - public void handleEvent (Selectable item, short events) { + public void handleEvent ( + long when, Selectable item, short events) { acceptConnection(); } + public void checkIdle (long now) { + // we're never idle + } }; _selset.add(_litem); @@ -168,6 +172,8 @@ public class ConnectionManager extends LoopingThread */ protected void iterate () { + long iterStamp = System.currentTimeMillis(); + // close any connections that have been queued up to die Connection dconn; while ((dconn = (Connection)_deathq.getNonBlocking()) != null) { @@ -179,6 +185,14 @@ public class ConnectionManager extends LoopingThread } } + // close any connections that have seen no network traffic for too + // long + int ccount = _selset.size(); + for (int ii = 0; ii < ccount; ii++) { + ((NetEventHandler)_selset.elementAt(ii).getObj()). + checkIdle(iterStamp); + } + // send any messages that are waiting on the outgoing queue Tuple tup; while ((tup = (Tuple)_outq.getNonBlocking()) != null) { @@ -221,7 +235,7 @@ public class ConnectionManager extends LoopingThread // connections network traffic from here on out try { RunningConnection rconn = - new RunningConnection(this, conn.getSocket()); + new RunningConnection(this, conn.getSocket(), iterStamp); // we need to keep using the same object input and output // streams from the beginning of the session because they // have contextual state that needs to be preserved @@ -252,7 +266,8 @@ public class ConnectionManager extends LoopingThread try { SelectItem item = active[i]; NetEventHandler handler = (NetEventHandler)item.obj; - handler.handleEvent(item.getSelectable(), item.revents); + handler.handleEvent( + iterStamp, item.getSelectable(), item.revents); } catch (Exception e) { Log.warning("Error processing network data."); diff --git a/src/java/com/threerings/presents/server/net/NetEventHandler.java b/src/java/com/threerings/presents/server/net/NetEventHandler.java index 1365661c3..0929f6342 100644 --- a/src/java/com/threerings/presents/server/net/NetEventHandler.java +++ b/src/java/com/threerings/presents/server/net/NetEventHandler.java @@ -1,5 +1,5 @@ // -// $Id: NetEventHandler.java,v 1.3 2001/10/11 04:07:53 mdb Exp $ +// $Id: NetEventHandler.java,v 1.4 2002/10/29 23:51:26 mdb Exp $ package com.threerings.presents.server.net; @@ -23,5 +23,11 @@ public interface NetEventHandler * The events parameter indicates which event or events * have occurred. */ - public void handleEvent (Selectable source, short events); + public void handleEvent (long when, Selectable source, short events); + + /** + * Called to ensure that this selectable has not been idle for longer + * than is possible in happily operating circumstances. + */ + public void checkIdle (long now); } diff --git a/src/java/com/threerings/presents/server/net/RunningConnection.java b/src/java/com/threerings/presents/server/net/RunningConnection.java index 121821071..b5d13533f 100644 --- a/src/java/com/threerings/presents/server/net/RunningConnection.java +++ b/src/java/com/threerings/presents/server/net/RunningConnection.java @@ -1,5 +1,5 @@ // -// $Id: RunningConnection.java,v 1.7 2002/09/24 00:50:27 mdb Exp $ +// $Id: RunningConnection.java,v 1.8 2002/10/29 23:51:26 mdb Exp $ package com.threerings.presents.server.net; @@ -20,10 +20,11 @@ public class RunningConnection extends Connection * client socket. */ public RunningConnection (ConnectionManager cmgr, - NonblockingSocket socket) + NonblockingSocket socket, + long createStamp) throws IOException { - super(cmgr, socket); + super(cmgr, socket, createStamp); } /**