From 98d19e055accf7290cd9cbe9e9ee24cd6a62ba66 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Tue, 29 Oct 2002 23:51:26 +0000 Subject: [PATCH] Added facilities for idling out clients that have not communicated with the server in 90 seconds. The client is set up to ping the server if it has had nothing to say to it for other reasons in the last 60 seconds. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1860 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- .../threerings/presents/client/Client.java | 7 +-- .../presents/client/Communicator.java | 44 ++++++++++++++++++- .../threerings/presents/net/PingRequest.java | 7 ++- .../server/net/AuthingConnection.java | 9 ++-- .../presents/server/net/Connection.java | 38 +++++++++++++--- .../server/net/ConnectionManager.java | 23 ++++++++-- .../presents/server/net/NetEventHandler.java | 10 ++++- .../server/net/RunningConnection.java | 7 +-- 8 files changed, 122 insertions(+), 23 deletions(-) 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); } /**