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;
@@ -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;
}
@@ -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;
@@ -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.
*/
@@ -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);
}
@@ -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;
}
@@ -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.");
@@ -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 <code>events</code> 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);
}
@@ -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);
}
/**