Files
sdk/runtime/bin/socket_impl.dart
T
sgjesse@google.com 086c1f0c3a Handle closing of HTTP client and server
For the server keep track of the state of the connection and close the
socket when it is closed by the client. The client might close the
connection both when processing a request and while the connection is
an idle keep alive connection.

For the client keep track of the active connections as well as the
idle keep alive connections. When the client is closed make sure to
close all connections.

R=ajohnsen@google.com

BUG=none
TEST=tests/standalone/src/io/HttpShutdownTest.dart

Review URL: https://chromiumcodereview.appspot.com//9589001

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@4932 260f80e4-7a28-3924-810f-c04153c831b5
2012-03-05 07:06:37 +00:00

490 lines
14 KiB
Dart

// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
class _SocketBase {
// Bit flags used when communicating between the eventhandler and
// dart code. The EVENT flags are used to indicate events of
// interest when sending a message from dart code to the
// eventhandler. When receiving a message from the eventhandler the
// EVENT flags indicate the events that actually happened. The
// COMMAND flags are used to send commands from dart to the
// eventhandler. COMMAND flags are never received from the
// eventhandler. Additional flags are used to communicate other
// information.
static final int _IN_EVENT = 0;
static final int _OUT_EVENT = 1;
static final int _ERROR_EVENT = 2;
static final int _CLOSE_EVENT = 3;
static final int _CLOSE_COMMAND = 8;
static final int _SHUTDOWN_READ_COMMAND = 9;
static final int _SHUTDOWN_WRITE_COMMAND = 10;
// Flag send to the eventhandler providing additional information on
// the type of the file descriptor.
static final int _LISTENING_SOCKET = 16;
static final int _PIPE = 17;
static final int _FIRST_EVENT = _IN_EVENT;
static final int _LAST_EVENT = _CLOSE_EVENT;
static final int _FIRST_COMMAND = _CLOSE_COMMAND;
static final int _LAST_COMMAND = _SHUTDOWN_WRITE_COMMAND;
_SocketBase () {
_handlerMap = new List(_LAST_EVENT + 1);
_handlerMask = 0;
_canActivateHandlers = true;
_id = -1;
_EventHandler._start();
_hashCode = _nextHashCode;
_nextHashCode = (_nextHashCode + 1) & 0xFFFFFFF;
}
// Multiplexes socket events to the socket handlers.
void _multiplex(int event_mask) {
_canActivateHandlers = false;
for (int i = _FIRST_EVENT; i <= _LAST_EVENT; i++) {
if (((event_mask & (1 << i)) != 0)) {
if ((i == _CLOSE_EVENT) && this is _Socket && _id >= 0) {
_closedRead = true;
if (_closedWrite) _close();
}
var eventHandler = _handlerMap[i];
if (eventHandler != null) {
// Unregister the out handler before executing it.
if (i == _OUT_EVENT) _setHandler(i, null);
// Don't call the in handler if there is no data available
// after all.
if ((i == _IN_EVENT) && (this is _Socket) && (available() == 0)) {
continue;
}
eventHandler();
}
}
}
_canActivateHandlers = true;
_activateHandlers();
}
void _setHandler(int event, void callback()) {
if (callback == null) {
_handlerMask &= ~(1 << event);
} else {
_handlerMask |= (1 << event);
}
_handlerMap[event] = callback;
// If the socket is only for writing then close the receive port
// when not waiting for any events.
if (this is _Socket &&
_closedRead &&
_handlerMask == 0 &&
_handler != null) {
_handler.close();
_handler = null;
} else {
_activateHandlers();
}
}
void _getPort() native "Socket_GetPort";
void set onError(void callback()) {
_setHandler(_ERROR_EVENT, callback);
}
void _activateHandlers() {
if (_canActivateHandlers && (_id >= 0)) {
if (_handlerMask == 0) {
if (_handler != null) {
_handler.close();
_handler = null;
}
return;
}
int data = _handlerMask;
if (_isListenSocket()) {
data |= (1 << _LISTENING_SOCKET);
} else {
if (_closedRead) { data &= ~(1 << _IN_EVENT); }
if (_closedWrite) { data &= ~(1 << _OUT_EVENT); }
if (_isPipe()) data |= (1 << _PIPE);
}
_sendToEventHandler(data);
}
}
int get port() {
if (_port === null) {
_port = _getPort();
}
return _port;
}
void close([bool halfClose = false]) {
if (_id >= 0) {
if (halfClose) {
_closeWrite();
} else {
_close();
}
} else if (_handler != null) {
// This is to support closing sockets created but never assigned
// any actual socket.
_handler.close();
_handler = null;
}
}
void _closeWrite() {
if (_id >= 0) {
if (_closedRead) {
_close();
} else {
_sendToEventHandler(1 << _SHUTDOWN_WRITE_COMMAND);
}
_closedWrite = true;
}
}
void _closeRead() {
if (_id >= 0) {
if (_closedWrite) {
_close();
} else {
_sendToEventHandler(1 << _SHUTDOWN_READ_COMMAND);
}
_closedRead = true;
}
}
void _close() {
if (_id >= 0) {
_sendToEventHandler(1 << _CLOSE_COMMAND);
_handler.close();
_handler = null;
_id = -1;
}
}
void _sendToEventHandler(int data) {
if (_handler === null) {
_handler = new ReceivePort();
_handler.receive((var message, ignored) { _multiplex(message); });
}
assert(_id >= 0);
_EventHandler._sendData(_id, _handler, data);
}
int hashCode() => _hashCode;
abstract bool _isListenSocket();
abstract bool _isPipe();
// Socket id is set from native. -1 indicates that the socket was closed.
int _id;
// Dedicated ReceivePort for socket events.
ReceivePort _handler;
// Poll event to handler map.
List _handlerMap;
// Indicates for which poll events the socket registered handlers.
int _handlerMask;
// Indicates if native interrupts can be activated.
bool _canActivateHandlers;
// Holds the port of the socket, null if not known.
int _port;
// Hash code for the socket. Currently this is just a counter.
int _hashCode;
static int _nextHashCode = 0;
}
class _ServerSocket extends _SocketBase implements ServerSocket {
// Constructor for server socket. First a socket object is allocated
// in which the native socket is stored. After that _createBind
// is called which creates a file descriptor and binds the given address
// and port to the socket. Null is returned if file descriptor creation or
// bind failed.
factory _ServerSocket(String bindAddress, int port, int backlog) {
ServerSocket socket = new _ServerSocket._internal();
if (!socket._createBindListen(bindAddress, port, backlog)) {
socket.close();
return null;
}
if (port != 0) {
socket._port = port;
}
return socket;
}
_ServerSocket._internal();
bool _accept(Socket socket) native "ServerSocket_Accept";
bool _createBindListen(String bindAddress, int port, int backlog)
native "ServerSocket_CreateBindListen";
void set onConnection(void callback(Socket connection)) {
_clientConnectionHandler = callback;
_setHandler(_IN_EVENT,
_clientConnectionHandler != null ? _connectionHandler : null);
}
void _connectionHandler() {
if (_id >= 0) {
_Socket socket = new _Socket._internal();
if (_accept(socket)) _clientConnectionHandler(socket);
}
}
bool _isListenSocket() => true;
bool _isPipe() => false;
var _clientConnectionHandler;
}
class _Socket extends _SocketBase implements Socket {
// Constructor for socket. First a socket object is allocated
// in which the native socket is stored. After that _createConnect is
// called which creates a file discriptor and connects to the given
// host on the given port. Null is returned if file descriptor creation
// or connect failed.
factory _Socket(String host, int port) {
Socket socket = new _Socket._internal();
if (!socket._createConnect(host, port)) {
socket.close();
return null;
}
return socket;
}
_Socket._internal();
_Socket._internalReadOnly() : _closedWrite = true, _pipe = true;
_Socket._internalWriteOnly() : _closedRead = true, _pipe = true;
int available() {
if (_id >= 0) {
return _available();
}
throw new
SocketIOException("Error: available failed - invalid socket handle");
}
int _available() native "Socket_Available";
bool get closed() => _closed;
int readList(List<int> buffer, int offset, int bytes) {
if (_id >= 0) {
if (bytes == 0) {
return 0;
}
if (offset < 0) {
throw new IndexOutOfRangeException(offset);
}
if (bytes < 0) {
throw new IndexOutOfRangeException(bytes);
}
if ((offset + bytes) > buffer.length) {
throw new IndexOutOfRangeException(offset + bytes);
}
int result = _readList(buffer, offset, bytes);
if (result < 0) {
_reportError();
}
return result;
}
throw new
SocketIOException("Error: readList failed - invalid socket handle");
}
int _readList(List<int> buffer, int offset, int bytes)
native "Socket_ReadList";
int writeList(List<int> buffer, int offset, int bytes) {
if (_id >= 0) {
if (bytes == 0) {
return 0;
}
if (offset < 0) {
throw new IndexOutOfRangeException(offset);
}
if (bytes < 0) {
throw new IndexOutOfRangeException(bytes);
}
if ((offset + bytes) > buffer.length) {
throw new IndexOutOfRangeException(offset + bytes);
}
// When using the Dart C API to access raw data, using a ByteArray is
// currently much faster. This function will make a copy of the
// supplied List to a ByteArray if it isn't already.
List outBuffer;
int outOffset = offset;
if (buffer is ByteArray || buffer is ObjectArray) {
outBuffer = buffer;
} else {
outBuffer = new ByteArray(bytes);
outOffset = 0;
int j = offset;
for (int i = 0; i < bytes; i++) {
int value = buffer[j];
if (value is! int) {
throw new FileIOException(
"List element is not an integer at index $j");
}
outBuffer[i] = value;
j++;
}
}
var bytes_written = _writeList(outBuffer, outOffset, bytes);
if (bytes_written < 0) {
// If writing fails we return 0 as the number of bytes and
// report the error on the error handler.
bytes_written = 0;
_reportError();
}
return bytes_written;
}
throw new
SocketIOException("Error: writeList failed - invalid socket handle");
}
int _writeList(List<int> buffer, int offset, int bytes)
native "Socket_WriteList";
void _reportError() {
// For all errors we close the socket, call the error handler and
// disable further calls of the error handler.
close();
var onError = _handlerMap[_ERROR_EVENT];
if (onError != null) {
onError();
_setHandler(_ERROR_EVENT, null);
}
}
bool _createConnect(String host, int port) native "Socket_CreateConnect";
void set onWrite(void callback()) {
if (_outputStream != null) throw new StreamException(
"Cannot set write handler when output stream is used");
_clientWriteHandler = callback;
_updateOutHandler();
}
void set onConnect(void callback()) {
if (_seenFirstOutEvent || _outputStream != null) {
throw new StreamException(
"Cannot set connect handler when already connected");
}
if (_outputStream != null) {
throw new StreamException(
"Cannot set connect handler when output stream is used");
}
_clientConnectHandler = callback;
_updateOutHandler();
}
void set onData(void callback()) {
if (_inputStream != null) throw new StreamException(
"Cannot set data handler when input stream is used");
_onData = callback;
}
void set onClosed(void callback()) {
if (_inputStream != null) throw new StreamException(
"Cannot set close handler when input stream is used");
_onClosed = callback;
}
bool _isListenSocket() => false;
bool _isPipe() => _pipe;
InputStream get inputStream() {
if (_inputStream === null) {
if (_handlerMap[_IN_EVENT] !== null ||
_handlerMap[_CLOSE_EVENT] !== null) {
throw new StreamException(
"Cannot get input stream when socket handlers are used");
}
_inputStream = new SocketInputStream(this);
}
return _inputStream;
}
OutputStream get outputStream() {
if (_outputStream === null) {
if (_handlerMap[_OUT_EVENT] !== null) {
throw new StreamException(
"Cannot get input stream when socket handlers are used");
}
_outputStream = new SocketOutputStream(this);
}
return _outputStream;
}
void set _onWrite(void callback()) {
_setHandler(_OUT_EVENT, callback);
}
void set _onData(void callback()) {
_setHandler(_IN_EVENT, callback);
}
void set _onClosed(void callback()) {
_setHandler(_CLOSE_EVENT, callback);
}
void _updateOutHandler() {
void firstWriteHandler() {
assert(!_seenFirstOutEvent);
_seenFirstOutEvent = true;
// From now on the write handler is only the client write
// handler (connect handler cannot be called again). Change this
// before calling any handlers as handlers can change the
// handlers.
if (_clientWriteHandler === null) _onWrite = _clientWriteHandler;
// First out event is socket connected event.
if (_clientConnectHandler !== null) _clientConnectHandler();
_clientConnectHandler = null;
// Always (even for the first out event) call the write handler.
if (_clientWriteHandler !== null) _clientWriteHandler();
}
if (_clientConnectHandler === null && _clientWriteHandler === null) {
_onWrite = null;
} else {
if (_seenFirstOutEvent) {
_onWrite = _clientWriteHandler;
} else {
_onWrite = firstWriteHandler;
}
}
}
bool _seenFirstOutEvent = false;
bool _closedRead = false;
bool _closedWrite = false;
bool _pipe = false;
Function _clientConnectHandler;
Function _clientWriteHandler;
SocketInputStream _inputStream;
SocketOutputStream _outputStream;
}