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
This commit is contained in:
Michael Bayne
2002-10-29 23:51:26 +00:00
parent af5b530b4c
commit 98d19e055a
8 changed files with 122 additions and 23 deletions
@@ -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; package com.threerings.presents.client;
@@ -224,8 +224,9 @@ public class Client
{ {
InvocationService isvc = getService(sclass); InvocationService isvc = getService(sclass);
if (isvc == null) { if (isvc == null) {
throw new RuntimeException(sclass.getName() + " isn't available. " + throw new RuntimeException(
"I can't bear to go on."); sclass.getName() + " isn't available. " +
"I can't bear to go on.");
} }
return isvc; return isvc;
} }
@@ -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; package com.threerings.presents.client;
@@ -13,6 +13,8 @@ import java.net.InetAddress;
import java.net.Socket; import java.net.Socket;
import com.samskivert.io.NestableIOException; import com.samskivert.io.NestableIOException;
import com.samskivert.util.Interval;
import com.samskivert.util.IntervalManager;
import com.samskivert.util.LoopingThread; import com.samskivert.util.LoopingThread;
import com.samskivert.util.Queue; import com.samskivert.util.Queue;
@@ -84,6 +86,16 @@ public class Communicator
// start up the writer thread if everything went successfully // start up the writer thread if everything went successfully
_reader = new Reader(); _reader = new Reader();
_reader.start(); _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; return;
} }
// stop our ping interval
if (_piid != -1) {
IntervalManager.remove(_piid);
}
// post a logoff message // post a logoff message
postMessage(new LogoffRequest()); postMessage(new LogoffRequest());
@@ -263,6 +280,28 @@ public class Communicator
// then write the framed message to actual output stream // then write the framed message to actual output stream
_fout.writeFrameAndReset(_out); _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 OutputStream _out;
protected Queue _msgq = new Queue(); protected Queue _msgq = new Queue();
protected int _piid;
protected long _lastWrite;
/** We use this to frame our upstream messages. */ /** We use this to frame our upstream messages. */
protected FramingOutputStream _fout; protected FramingOutputStream _fout;
protected ObjectOutputStream _oout; protected ObjectOutputStream _oout;
@@ -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; package com.threerings.presents.net;
@@ -10,6 +10,11 @@ import com.threerings.io.ObjectOutputStream;
public class PingRequest extends UpstreamMessage 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. * Zero argument constructor used when unserializing an instance.
*/ */
@@ -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; package com.threerings.presents.server.net;
@@ -16,8 +16,8 @@ import com.threerings.presents.net.UpstreamMessage;
* The authing connection manages the client connection until * The authing connection manages the client connection until
* authentication has completed (for better or for worse). * authentication has completed (for better or for worse).
*/ */
public class AuthingConnection public class AuthingConnection extends Connection
extends Connection implements MessageHandler implements MessageHandler
{ {
/** /**
* Creates a new authing connection object that will manage the * Creates a new authing connection object that will manage the
@@ -27,7 +27,8 @@ public class AuthingConnection
NonblockingSocket socket) NonblockingSocket socket)
throws IOException throws IOException
{ {
super(cmgr, socket); super(cmgr, socket, System.currentTimeMillis());
// we are our own message handler // we are our own message handler
setMessageHandler(this); setMessageHandler(this);
} }
@@ -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; package com.threerings.presents.server.net;
@@ -7,6 +7,7 @@ import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.SocketTimeoutException;
import ninja2.core.io_core.nbio.*; import ninja2.core.io_core.nbio.*;
@@ -19,6 +20,7 @@ import com.threerings.io.ObjectOutputStream;
import com.threerings.presents.Log; import com.threerings.presents.Log;
import com.threerings.presents.net.DownstreamMessage; import com.threerings.presents.net.DownstreamMessage;
import com.threerings.presents.net.PingRequest;
import com.threerings.presents.net.UpstreamMessage; 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 * @param cmgr The connection manager with which this connection is
* associated. * associated.
* @param socket The socket from which we'll be reading messages. * @param socket The socket from which we'll be reading messages.
* @param fout The framing output stream via which we'll write * @param createStamp The time at which this connection was created.
* serialized messages to the client.
*/ */
public Connection (ConnectionManager cmgr, NonblockingSocket socket) public Connection (ConnectionManager cmgr, NonblockingSocket socket,
long createStamp)
throws IOException throws IOException
{ {
_cmgr = cmgr; _cmgr = cmgr;
_socket = socket; _socket = socket;
_lastEvent = createStamp;
// create a select item that will allow us to be incorporated into // create a select item that will allow us to be incorporated into
// the main event polling loop // 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. * 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 { try {
// we're lazy about creating our input streams because we may // we're lazy about creating our input streams because we may
// be inheriting them from our authing connection and we don't // 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 * Posts a downstream message for delivery to this connection. The
* message will be delivered by the conmgr thread as soon as it gets * 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 NonblockingSocket _socket;
protected SelectItem _selitem; protected SelectItem _selitem;
protected long _lastEvent;
protected InputStream _in; protected InputStream _in;
protected FramedInputStream _fin; protected FramedInputStream _fin;
protected ObjectInputStream _oin; protected ObjectInputStream _oin;
@@ -271,4 +294,9 @@ public abstract class Connection implements NetEventHandler
protected ObjectOutputStream _oout; protected ObjectOutputStream _oout;
protected MessageHandler _handler; 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;
} }
@@ -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; package com.threerings.presents.server.net;
@@ -54,9 +54,13 @@ public class ConnectionManager extends LoopingThread
_litem = new SelectItem(_listener, Selectable.ACCEPT_READY); _litem = new SelectItem(_listener, Selectable.ACCEPT_READY);
// when an ACCEPT_READY event happens, we do this: // when an ACCEPT_READY event happens, we do this:
_litem.obj = new NetEventHandler() { _litem.obj = new NetEventHandler() {
public void handleEvent (Selectable item, short events) { public void handleEvent (
long when, Selectable item, short events) {
acceptConnection(); acceptConnection();
} }
public void checkIdle (long now) {
// we're never idle
}
}; };
_selset.add(_litem); _selset.add(_litem);
@@ -168,6 +172,8 @@ public class ConnectionManager extends LoopingThread
*/ */
protected void iterate () protected void iterate ()
{ {
long iterStamp = System.currentTimeMillis();
// close any connections that have been queued up to die // close any connections that have been queued up to die
Connection dconn; Connection dconn;
while ((dconn = (Connection)_deathq.getNonBlocking()) != null) { 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 // send any messages that are waiting on the outgoing queue
Tuple tup; Tuple tup;
while ((tup = (Tuple)_outq.getNonBlocking()) != null) { while ((tup = (Tuple)_outq.getNonBlocking()) != null) {
@@ -221,7 +235,7 @@ public class ConnectionManager extends LoopingThread
// connections network traffic from here on out // connections network traffic from here on out
try { try {
RunningConnection rconn = RunningConnection rconn =
new RunningConnection(this, conn.getSocket()); new RunningConnection(this, conn.getSocket(), iterStamp);
// we need to keep using the same object input and output // we need to keep using the same object input and output
// streams from the beginning of the session because they // streams from the beginning of the session because they
// have contextual state that needs to be preserved // have contextual state that needs to be preserved
@@ -252,7 +266,8 @@ public class ConnectionManager extends LoopingThread
try { try {
SelectItem item = active[i]; SelectItem item = active[i];
NetEventHandler handler = (NetEventHandler)item.obj; NetEventHandler handler = (NetEventHandler)item.obj;
handler.handleEvent(item.getSelectable(), item.revents); handler.handleEvent(
iterStamp, item.getSelectable(), item.revents);
} catch (Exception e) { } catch (Exception e) {
Log.warning("Error processing network data."); Log.warning("Error processing network data.");
@@ -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; package com.threerings.presents.server.net;
@@ -23,5 +23,11 @@ public interface NetEventHandler
* The <code>events</code> parameter indicates which event or events * The <code>events</code> parameter indicates which event or events
* have occurred. * 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);
} }
@@ -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; package com.threerings.presents.server.net;
@@ -20,10 +20,11 @@ public class RunningConnection extends Connection
* client socket. * client socket.
*/ */
public RunningConnection (ConnectionManager cmgr, public RunningConnection (ConnectionManager cmgr,
NonblockingSocket socket) NonblockingSocket socket,
long createStamp)
throws IOException throws IOException
{ {
super(cmgr, socket); super(cmgr, socket, createStamp);
} }
/** /**