Make HTTP server close sockets when closed in idle state

If a connection was in the idle state and the socket was closed by the
client the server did close the connection. This caused the server to
quickly run out of resources. There was a state check missing after a
state change.

Added a COSING state as well and a way to get the number of
connections if various states.

R=ager@google.com, ajohnsen@google.com

BUG=

Review URL: https://codereview.chromium.org//11348127

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@15069 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
sgjesse@google.com 2012-11-19 11:11:00 +00:00
parent 88a921030d
commit 6e09071a58
3 changed files with 128 additions and 4 deletions

View file

@ -117,6 +117,41 @@ abstract class HttpServer {
* is 20 minutes.
*/
set sessionTimeout(int timeout);
/**
* Returns a [:HttpConnectionsInfo:] object with an overview of the
* current connection handled by the server.
*/
HttpConnectionsInfo connectionsInfo();
}
/**
* Overview information of the [:HttpServer:] socket connections.
*/
class HttpConnectionsInfo {
/**
* Total number of socket connections.
*/
int total = 0;
/**
* Number of active connections where actual request/response
* processing is active.
*/
int active = 0;
/**
* Number of idle connections held by clients as persistent connections.
*/
int idle = 0;
/**
* Number of connections which are preparing to close. Note: These
* connections are also part of the [:active:] count as they might
* still be sending data to the client before finally closing.
*/
int closing = 0;
}

View file

@ -27,6 +27,7 @@ class _CloseQueue {
return;
}
connection._state |= _HttpConnectionBase.CLOSING;
_q.add(connection);
// If output stream is not closed for writing close it now and
@ -49,6 +50,7 @@ class _CloseQueue {
// information for anything. For both server and client
// connections the inbound message have been read to
// completion when the socket enters the close queue.
closeIfDone();
};
} else {
connection._socket.onClosed = () { assert(false); };
@ -718,17 +720,21 @@ class _HttpOutputStream extends _BaseOutputStream implements OutputStream {
abstract class _HttpConnectionBase {
static const int IDLE = 0;
static const int ACTIVE = 1;
static const int REQUEST_DONE = 2;
static const int RESPONSE_DONE = 4;
static const int CLOSING = 2;
static const int REQUEST_DONE = 4;
static const int RESPONSE_DONE = 8;
static const int ALL_DONE = REQUEST_DONE | RESPONSE_DONE;
static const int READ_CLOSED = 8;
static const int WRITE_CLOSED = 16;
static const int READ_CLOSED = 16;
static const int WRITE_CLOSED = 32;
static const int FULLY_CLOSED = READ_CLOSED | WRITE_CLOSED;
_HttpConnectionBase() : hashCode = _nextHashCode {
_nextHashCode = (_nextHashCode + 1) & 0xFFFFFFF;
}
bool get _isIdle => (_state & ACTIVE) == 0;
bool get _isActive => (_state & ACTIVE) == ACTIVE;
bool get _isClosing => (_state & CLOSING) == CLOSING;
bool get _isRequestDone => (_state & REQUEST_DONE) == REQUEST_DONE;
bool get _isResponseDone => (_state & RESPONSE_DONE) == RESPONSE_DONE;
bool get _isAllDone => (_state & ALL_DONE) == ALL_DONE;
@ -832,6 +838,7 @@ class _HttpConnection extends _HttpConnectionBase {
void _onClosed() {
_state |= _HttpConnectionBase.READ_CLOSED;
_checkDone();
}
void _onError(e) {
@ -884,6 +891,9 @@ class _HttpConnection extends _HttpConnectionBase {
} else {
_state = _HttpConnectionBase.IDLE;
}
} else if (_state == _HttpConnectionBase.READ_CLOSED) {
// If entering READ_CLOSED state while idle close the connection.
_server._closeQueue.add(this);
}
}
@ -1024,6 +1034,21 @@ class _HttpServer implements HttpServer {
return _sessionManagerInstance;
}
HttpConnectionsInfo connectionsInfo() {
HttpConnectionsInfo result = new HttpConnectionsInfo();
result.total = _connections.length;
_connections.forEach((_HttpConnection conn) {
if (conn._isActive) {
result.active++;
} else if (conn._isIdle) {
result.idle++;
} else {
assert(result._isClosing);
result.closing++;
}
});
return result;
}
ServerSocket _server; // The server listen socket.
bool _closeServer = false;
@ -1435,6 +1460,7 @@ class _HttpClientConnection
void _onClosed() {
_state |= _HttpConnectionBase.READ_CLOSED;
_checkSocketDone();
}
void _onError(e) {

View file

@ -95,6 +95,66 @@ void test3(int totalConnections) {
}
void test4() {
var server = new HttpServer();
server.listen("127.0.0.1", 0);
server.defaultRequestHandler = (var request, var response) {
request.inputStream.onClosed = () {
new Timer.repeating(100, (timer) {
if (server.connectionsInfo().total == 0) {
server.close();
timer.cancel();
}
});
response.outputStream.close();
};
};
var client= new HttpClient();
var conn = client.get("127.0.0.1", server.port, "/");
conn.onResponse = (var response) {
response.inputStream.onClosed = () {
client.shutdown();
};
};
}
void test5(int totalConnections) {
var server = new HttpServer();
server.listen("127.0.0.1", 0, backlog: totalConnections);
server.defaultRequestHandler = (var request, var response) {
request.inputStream.onClosed = () {
response.outputStream.close();
};
};
server.onError = (e) => { };
// Create a number of client requests and keep then active. Then
// close the client and wait for the server to lose all active
// connections.
var client= new HttpClient();
for (int i = 0; i < totalConnections; i++) {
var conn = client.post("127.0.0.1", server.port, "/");
conn.onRequest = (req) { req.outputStream.write([0]); };
}
bool clientClosed = false;
new Timer.repeating(100, (timer) {
if (!clientClosed) {
if (server.connectionsInfo().total == totalConnections) {
clientClosed = true;
client.shutdown();
}
} else {
if (server.connectionsInfo().total == 0) {
server.close();
timer.cancel();
}
}
});
}
void main() {
test1(1);
test1(10);
@ -102,4 +162,7 @@ void main() {
test2(10);
test3(1);
test3(10);
test4();
test5(1);
test5(10);
}