Commit 57481c35 authored by Julien Muchembled's avatar Julien Muchembled

Review API betweeen connections and connectors

- Review error handling. Only 2 exceptions remain in connector.py:

  - Drop useless exception handling for EAGAIN since it should not happen
    if the kernel says the socket is ready.
  - Do not distinguish other socket errors. Just close and log in a generic way.
  - No need to raise a specific exception for EOF.
  - Make 'connect' return a boolean instead of raising an exception.
  - Raise appropriate exception when answer/ask/notify is called on a closed
    non-MT connection.

- Add support for more complex connectors, which may need to write for a read
  operation, or to read when there's pending data to send. This will be
  required for SSL support (more exactly, the handshake will be done in
  a transparent way):

  - Move write buffer to connector.
  - Make 'receive' fill the read buffer, instead of returning the read data.
  - Make 'receive' & 'send' return a boolean to switch polling for writing.
  - Tolerate that sockets return 0 as number of bytes sent.

- In testConnection, simply delete all failing tests, as announced
  in commit 71e30fb9.
parent 36a32f23
This diff is collapsed.
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
import socket import socket
import errno import errno
from time import time from time import time
from . import logging
# Global connector registry. # Global connector registry.
# Fill by calling registerConnectorHandler. # Fill by calling registerConnectorHandler.
...@@ -56,8 +57,19 @@ class SocketConnector(object): ...@@ -56,8 +57,19 @@ class SocketConnector(object):
s.setblocking(0) s.setblocking(0)
# disable Nagle algorithm to reduce latency # disable Nagle algorithm to reduce latency
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
self.queued = []
return self return self
def queue(self, data):
was_empty = not self.queued
self.queued += data
return was_empty
def _error(self, op, exc):
logging.debug("%s failed for %s: %s (%s)",
op, self, errno.errorcode[exc.errno], exc.strerror)
raise ConnectorException
# Threaded tests monkey-patch the following 2 operations. # Threaded tests monkey-patch the following 2 operations.
_connect = lambda self, addr: self.socket.connect(addr) _connect = lambda self, addr: self.socket.connect(addr)
_bind = lambda self, addr: self.socket.bind(addr) _bind = lambda self, addr: self.socket.bind(addr)
...@@ -68,20 +80,23 @@ class SocketConnector(object): ...@@ -68,20 +80,23 @@ class SocketConnector(object):
try: try:
connect_limit = self.connect_limit[addr] connect_limit = self.connect_limit[addr]
if time() < connect_limit: if time() < connect_limit:
# Next call to queue() must return False
# in order not to enable polling for writing.
self.queued or self.queued.append('')
raise ConnectorDelayedConnection(connect_limit) raise ConnectorDelayedConnection(connect_limit)
if self.queued and not self.queued[0]:
del self.queued[0]
except KeyError: except KeyError:
pass pass
self.connect_limit[addr] = time() + self.CONNECT_LIMIT self.connect_limit[addr] = time() + self.CONNECT_LIMIT
self.is_server = self.is_closed = False self.is_server = self.is_closed = False
try: try:
self._connect(addr) self._connect(addr)
except socket.error, (err, errmsg): except socket.error, e:
if err == errno.EINPROGRESS: if e.errno == errno.EINPROGRESS:
raise ConnectorInProgressException return False
if err == errno.ECONNREFUSED: self._error('connect', e)
raise ConnectorConnectionRefusedException return True
raise ConnectorException, 'makeClientConnection to %s failed:' \
' %s:%s' % (addr, err, errmsg)
def makeListeningConnection(self): def makeListeningConnection(self):
assert self.is_closed is None assert self.is_closed is None
...@@ -90,10 +105,9 @@ class SocketConnector(object): ...@@ -90,10 +105,9 @@ class SocketConnector(object):
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self._bind(self.addr) self._bind(self.addr)
self.socket.listen(5) self.socket.listen(5)
except socket.error, (err, errmsg): except socket.error, e:
self.socket.close() self.socket.close()
raise ConnectorException, 'makeListeningConnection on %s failed:' \ self._error('listen', e)
' %s:%s' % (addr, err, errmsg)
def getError(self): def getError(self):
return self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) return self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
...@@ -116,33 +130,39 @@ class SocketConnector(object): ...@@ -116,33 +130,39 @@ class SocketConnector(object):
s, addr = self.socket.accept() s, addr = self.socket.accept()
s = self.__class__(addr, s) s = self.__class__(addr, s)
return s, s.addr return s, s.addr
except socket.error, (err, errmsg): except socket.error, e:
if err == errno.EAGAIN: self._error('accept', e)
raise ConnectorTryAgainException
raise ConnectorException, 'accept failed: %s:%s' % \
(err, errmsg)
def receive(self): def receive(self, read_buf):
try: try:
return self.socket.recv(4096) data = self.socket.recv(4096)
except socket.error, (err, errmsg): except socket.error, e:
if err == errno.EAGAIN: self._error('recv', e)
raise ConnectorTryAgainException if data:
if err in (errno.ECONNREFUSED, errno.EHOSTUNREACH): read_buf.append(data)
raise ConnectorConnectionRefusedException return
if err in (errno.ECONNRESET, errno.ETIMEDOUT): logging.debug('%r closed in recv', self)
raise ConnectorConnectionClosedException raise ConnectorException
raise ConnectorException, 'receive failed: %s:%s' % (err, errmsg)
def send(self):
def send(self, msg): msg = ''.join(self.queued)
try: if msg:
return self.socket.send(msg) try:
except socket.error, (err, errmsg): n = self.socket.send(msg)
if err == errno.EAGAIN: except socket.error, e:
raise ConnectorTryAgainException self._error('send', e)
if err in (errno.ECONNRESET, errno.ETIMEDOUT, errno.EPIPE): # Do nothing special if n == 0:
raise ConnectorConnectionClosedException # - it never happens for simple sockets;
raise ConnectorException, 'send failed: %s:%s' % (err, errmsg) # - for SSL sockets, this is always the case unless everything
# could be sent.
if n != len(msg):
self.queued[:] = msg[n:],
return False
del self.queued[:]
else:
assert not self.queued
return True
def close(self): def close(self):
self.is_closed = True self.is_closed = True
...@@ -195,17 +215,5 @@ registerConnectorHandler(SocketConnectorIPv6) ...@@ -195,17 +215,5 @@ registerConnectorHandler(SocketConnectorIPv6)
class ConnectorException(Exception): class ConnectorException(Exception):
pass pass
class ConnectorTryAgainException(ConnectorException):
pass
class ConnectorInProgressException(ConnectorException):
pass
class ConnectorConnectionClosedException(ConnectorException):
pass
class ConnectorConnectionRefusedException(ConnectorException):
pass
class ConnectorDelayedConnection(ConnectorException): class ConnectorDelayedConnection(ConnectorException):
pass pass
...@@ -16,8 +16,7 @@ ...@@ -16,8 +16,7 @@
from collections import deque from collections import deque
from neo.lib import logging from neo.lib import logging
from neo.lib.connection import ClientConnection from neo.lib.connection import ClientConnection, ConnectionClosed
from neo.lib.connector import ConnectorConnectionClosedException
from neo.lib.protocol import NodeTypes, Packets, ZERO_OID from neo.lib.protocol import NodeTypes, Packets, ZERO_OID
from neo.lib.util import add64, dump from neo.lib.util import add64, dump
from .handlers.storage import StorageOperationHandler from .handlers.storage import StorageOperationHandler
...@@ -85,7 +84,7 @@ class Checker(object): ...@@ -85,7 +84,7 @@ class Checker(object):
if self.conn_dict: if self.conn_dict:
break break
msg = "no replica" msg = "no replica"
except ConnectorConnectionClosedException: except ConnectionClosed:
msg = "connection closed" msg = "connection closed"
finally: finally:
conn_set.update(self.conn_dict) conn_set.update(self.conn_dict)
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
import weakref import weakref
from functools import wraps from functools import wraps
from neo.lib.connector import ConnectorConnectionClosedException from neo.lib.connection import ConnectionClosed
from neo.lib.handler import EventHandler from neo.lib.handler import EventHandler
from neo.lib.protocol import Errors, NodeStates, Packets, ProtocolError, \ from neo.lib.protocol import Errors, NodeStates, Packets, ProtocolError, \
ZERO_HASH ZERO_HASH
...@@ -154,7 +154,7 @@ class StorageOperationHandler(EventHandler): ...@@ -154,7 +154,7 @@ class StorageOperationHandler(EventHandler):
r = app.dm.checkTIDRange(*args) r = app.dm.checkTIDRange(*args)
try: try:
conn.answer(Packets.AnswerCheckTIDRange(*r), msg_id) conn.answer(Packets.AnswerCheckTIDRange(*r), msg_id)
except (weakref.ReferenceError, ConnectorConnectionClosedException): except (weakref.ReferenceError, ConnectionClosed):
pass pass
yield yield
app.newTask(check()) app.newTask(check())
...@@ -170,7 +170,7 @@ class StorageOperationHandler(EventHandler): ...@@ -170,7 +170,7 @@ class StorageOperationHandler(EventHandler):
r = app.dm.checkSerialRange(*args) r = app.dm.checkSerialRange(*args)
try: try:
conn.answer(Packets.AnswerCheckSerialRange(*r), msg_id) conn.answer(Packets.AnswerCheckSerialRange(*r), msg_id)
except (weakref.ReferenceError, ConnectorConnectionClosedException): except (weakref.ReferenceError, ConnectionClosed):
pass pass
yield yield
app.newTask(check()) app.newTask(check())
...@@ -211,7 +211,7 @@ class StorageOperationHandler(EventHandler): ...@@ -211,7 +211,7 @@ class StorageOperationHandler(EventHandler):
conn.answer(Packets.AnswerFetchTransactions( conn.answer(Packets.AnswerFetchTransactions(
pack_tid, next_tid, peer_tid_set), msg_id) pack_tid, next_tid, peer_tid_set), msg_id)
yield yield
except (weakref.ReferenceError, ConnectorConnectionClosedException): except (weakref.ReferenceError, ConnectionClosed):
pass pass
app.newTask(push()) app.newTask(push())
...@@ -253,6 +253,6 @@ class StorageOperationHandler(EventHandler): ...@@ -253,6 +253,6 @@ class StorageOperationHandler(EventHandler):
conn.answer(Packets.AnswerFetchObjects( conn.answer(Packets.AnswerFetchObjects(
pack_tid, next_tid, next_oid, object_dict), msg_id) pack_tid, next_tid, next_oid, object_dict), msg_id)
yield yield
except (weakref.ReferenceError, ConnectorConnectionClosedException): except (weakref.ReferenceError, ConnectionClosed):
pass pass
app.newTask(push()) app.newTask(push())
This diff is collapsed.
...@@ -31,8 +31,7 @@ import neo.client.app, neo.neoctl.app ...@@ -31,8 +31,7 @@ import neo.client.app, neo.neoctl.app
from neo.client import Storage from neo.client import Storage
from neo.lib import logging from neo.lib import logging
from neo.lib.connection import BaseConnection, Connection from neo.lib.connection import BaseConnection, Connection
from neo.lib.connector import SocketConnector, \ from neo.lib.connector import SocketConnector, ConnectorException
ConnectorConnectionRefusedException
from neo.lib.locking import SimpleQueue from neo.lib.locking import SimpleQueue
from neo.lib.protocol import CellStates, ClusterStates, NodeStates, NodeTypes from neo.lib.protocol import CellStates, ClusterStates, NodeStates, NodeTypes
from neo.lib.util import cached_property, parseMasterList, p64 from neo.lib.util import cached_property, parseMasterList, p64
...@@ -322,7 +321,7 @@ class ServerNode(Node): ...@@ -322,7 +321,7 @@ class ServerNode(Node):
try: try:
return self.listening_conn.getAddress() return self.listening_conn.getAddress()
except AttributeError: except AttributeError:
raise ConnectorConnectionRefusedException raise ConnectorException
class AdminApplication(ServerNode, neo.admin.app.Application): class AdminApplication(ServerNode, neo.admin.app.Application):
pass pass
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment