|
|
@ -1,56 +1,11 @@ |
|
|
|
# -*- Mode: Python -*- |
|
|
|
# Id: asyncore.py,v 2.51 2000/09/07 22:29:26 rushing Exp |
|
|
|
# Author: Sam Rushing <rushing@nightmare.com> |
|
|
|
# pylint: disable=too-many-statements,too-many-branches,no-self-use,too-many-lines,attribute-defined-outside-init |
|
|
|
# pylint: disable=global-statement |
|
|
|
""" |
|
|
|
src/network/asyncore_pollchoose.py |
|
|
|
================================== |
|
|
|
|
|
|
|
# ====================================================================== |
|
|
|
# Copyright 1996 by Sam Rushing |
|
|
|
# |
|
|
|
# All Rights Reserved |
|
|
|
# |
|
|
|
# Permission to use, copy, modify, and distribute this software and |
|
|
|
# its documentation for any purpose and without fee is hereby |
|
|
|
# granted, provided that the above copyright notice appear in all |
|
|
|
# copies and that both that copyright notice and this permission |
|
|
|
# notice appear in supporting documentation, and that the name of Sam |
|
|
|
# Rushing not be used in advertising or publicity pertaining to |
|
|
|
# distribution of the software without specific, written prior |
|
|
|
# permission. |
|
|
|
# |
|
|
|
# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, |
|
|
|
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN |
|
|
|
# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR |
|
|
|
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS |
|
|
|
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, |
|
|
|
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
|
|
|
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
|
|
# ====================================================================== |
|
|
|
|
|
|
|
Basic infrastructure for asynchronous socket service clients and servers. |
|
|
|
|
|
|
|
There are only two ways to have a program on a single processor do "more |
|
|
|
than one thing at a time". Multi-threaded programming is the simplest and |
|
|
|
most popular way to do it, but there is another very different technique, |
|
|
|
that lets you have nearly all the advantages of multi-threading, without |
|
|
|
actually using multiple threads. it's really only practical if your program |
|
|
|
is largely I/O bound. If your program is CPU bound, then pre-emptive |
|
|
|
scheduled threads are probably what you really need. Network servers are |
|
|
|
rarely CPU-bound, however. |
|
|
|
|
|
|
|
If your operating system supports the select() system call in its I/O |
|
|
|
library (and nearly all do), then you can use it to juggle multiple |
|
|
|
communication channels at once; doing other work while your I/O is taking |
|
|
|
place in the "background." Although this strategy can seem strange and |
|
|
|
complex, especially at first, it is in many ways easier to understand and |
|
|
|
control than multi-threaded programming. The module documented here solves |
|
|
|
many of the difficult problems for you, making the task of building |
|
|
|
sophisticated high-performance network servers and clients a snap. |
|
|
|
""" |
|
|
|
|
|
|
|
# -*- Mode: Python -*- |
|
|
|
# Id: asyncore.py,v 2.51 2000/09/07 22:29:26 rushing Exp |
|
|
|
# Author: Sam Rushing <rushing@nightmare.com> |
|
|
|
# pylint: disable=too-many-branches,too-many-lines,global-statement |
|
|
|
# pylint: disable=redefined-builtin,no-self-use |
|
|
|
import os |
|
|
|
import select |
|
|
|
import socket |
|
|
@ -58,8 +13,9 @@ import sys |
|
|
|
import time |
|
|
|
import warnings |
|
|
|
from errno import ( |
|
|
|
EADDRINUSE, EAGAIN, EALREADY, EBADF, ECONNABORTED, ECONNREFUSED, ECONNRESET, EHOSTUNREACH, EINPROGRESS, EINTR, |
|
|
|
EINVAL, EISCONN, ENETUNREACH, ENOTCONN, ENOTSOCK, EPIPE, ESHUTDOWN, ETIMEDOUT, EWOULDBLOCK, errorcode |
|
|
|
EADDRINUSE, EAGAIN, EALREADY, EBADF, ECONNABORTED, ECONNREFUSED, |
|
|
|
ECONNRESET, EHOSTUNREACH, EINPROGRESS, EINTR, EINVAL, EISCONN, ENETUNREACH, |
|
|
|
ENOTCONN, ENOTSOCK, EPIPE, ESHUTDOWN, ETIMEDOUT, EWOULDBLOCK, errorcode |
|
|
|
) |
|
|
|
from threading import current_thread |
|
|
|
|
|
|
@ -107,7 +63,8 @@ def _strerror(err): |
|
|
|
|
|
|
|
|
|
|
|
class ExitNow(Exception): |
|
|
|
"""We don't use directly but may be necessary as we replace asyncore due to some library raising or expecting it""" |
|
|
|
"""We don't use directly but may be necessary as we replace |
|
|
|
asyncore due to some library raising or expecting it""" |
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
@ -152,7 +109,8 @@ def write(obj): |
|
|
|
def set_rates(download, upload): |
|
|
|
"""Set throttling rates""" |
|
|
|
|
|
|
|
global maxDownloadRate, maxUploadRate, downloadBucket, uploadBucket, downloadTimestamp, uploadTimestamp |
|
|
|
global maxDownloadRate, maxUploadRate, downloadBucket |
|
|
|
global uploadBucket, downloadTimestamp, uploadTimestamp |
|
|
|
|
|
|
|
maxDownloadRate = float(download) * 1024 |
|
|
|
maxUploadRate = float(upload) * 1024 |
|
|
@ -182,7 +140,8 @@ def update_received(download=0): |
|
|
|
currentTimestamp = time.time() |
|
|
|
receivedBytes += download |
|
|
|
if maxDownloadRate > 0: |
|
|
|
bucketIncrease = maxDownloadRate * (currentTimestamp - downloadTimestamp) |
|
|
|
bucketIncrease = \ |
|
|
|
maxDownloadRate * (currentTimestamp - downloadTimestamp) |
|
|
|
downloadBucket += bucketIncrease |
|
|
|
if downloadBucket > maxDownloadRate: |
|
|
|
downloadBucket = int(maxDownloadRate) |
|
|
@ -242,7 +201,6 @@ def readwrite(obj, flags): |
|
|
|
|
|
|
|
def select_poller(timeout=0.0, map=None): |
|
|
|
"""A poller which uses select(), available on most platforms.""" |
|
|
|
# pylint: disable=redefined-builtin |
|
|
|
|
|
|
|
if map is None: |
|
|
|
map = socket_map |
|
|
@ -298,7 +256,6 @@ def select_poller(timeout=0.0, map=None): |
|
|
|
|
|
|
|
def poll_poller(timeout=0.0, map=None): |
|
|
|
"""A poller which uses poll(), available on most UNIXen.""" |
|
|
|
# pylint: disable=redefined-builtin |
|
|
|
|
|
|
|
if map is None: |
|
|
|
map = socket_map |
|
|
@ -356,7 +313,6 @@ poll2 = poll3 = poll_poller |
|
|
|
|
|
|
|
def epoll_poller(timeout=0.0, map=None): |
|
|
|
"""A poller which uses epoll(), supported on Linux 2.5.44 and newer.""" |
|
|
|
# pylint: disable=redefined-builtin |
|
|
|
|
|
|
|
if map is None: |
|
|
|
map = socket_map |
|
|
@ -412,7 +368,7 @@ def epoll_poller(timeout=0.0, map=None): |
|
|
|
|
|
|
|
def kqueue_poller(timeout=0.0, map=None): |
|
|
|
"""A poller which uses kqueue(), BSD specific.""" |
|
|
|
# pylint: disable=redefined-builtin,no-member |
|
|
|
# pylint: disable=no-member,too-many-statements |
|
|
|
|
|
|
|
if map is None: |
|
|
|
map = socket_map |
|
|
@ -440,14 +396,20 @@ def kqueue_poller(timeout=0.0, map=None): |
|
|
|
poller_flags |= select.KQ_EV_ENABLE |
|
|
|
else: |
|
|
|
poller_flags |= select.KQ_EV_DISABLE |
|
|
|
updates.append(select.kevent(fd, filter=select.KQ_FILTER_READ, flags=poller_flags)) |
|
|
|
updates.append( |
|
|
|
select.kevent( |
|
|
|
fd, filter=select.KQ_FILTER_READ, |
|
|
|
flags=poller_flags)) |
|
|
|
if kq_filter & 2 != obj.poller_filter & 2: |
|
|
|
poller_flags = select.KQ_EV_ADD |
|
|
|
if kq_filter & 2: |
|
|
|
poller_flags |= select.KQ_EV_ENABLE |
|
|
|
else: |
|
|
|
poller_flags |= select.KQ_EV_DISABLE |
|
|
|
updates.append(select.kevent(fd, filter=select.KQ_FILTER_WRITE, flags=poller_flags)) |
|
|
|
updates.append( |
|
|
|
select.kevent( |
|
|
|
fd, filter=select.KQ_FILTER_WRITE, |
|
|
|
flags=poller_flags)) |
|
|
|
obj.poller_filter = kq_filter |
|
|
|
|
|
|
|
if not selectables: |
|
|
@ -481,7 +443,6 @@ def kqueue_poller(timeout=0.0, map=None): |
|
|
|
|
|
|
|
def loop(timeout=30.0, use_poll=False, map=None, count=None, poller=None): |
|
|
|
"""Poll in a loop, until count or timeout is reached""" |
|
|
|
# pylint: disable=redefined-builtin |
|
|
|
|
|
|
|
if map is None: |
|
|
|
map = socket_map |
|
|
@ -520,9 +481,9 @@ def loop(timeout=30.0, use_poll=False, map=None, count=None, poller=None): |
|
|
|
count = count - 1 |
|
|
|
|
|
|
|
|
|
|
|
class dispatcher: |
|
|
|
class dispatcher(object): |
|
|
|
"""Dispatcher for socket objects""" |
|
|
|
# pylint: disable=too-many-public-methods,too-many-instance-attributes,old-style-class |
|
|
|
# pylint: disable=too-many-public-methods,too-many-instance-attributes |
|
|
|
|
|
|
|
debug = False |
|
|
|
connected = False |
|
|
@ -537,7 +498,6 @@ class dispatcher: |
|
|
|
minTx = 1500 |
|
|
|
|
|
|
|
def __init__(self, sock=None, map=None): |
|
|
|
# pylint: disable=redefined-builtin |
|
|
|
if map is None: |
|
|
|
self._map = socket_map |
|
|
|
else: |
|
|
@ -586,8 +546,7 @@ class dispatcher: |
|
|
|
|
|
|
|
def add_channel(self, map=None): |
|
|
|
"""Add a channel""" |
|
|
|
# pylint: disable=redefined-builtin |
|
|
|
|
|
|
|
# pylint: disable=attribute-defined-outside-init |
|
|
|
if map is None: |
|
|
|
map = self._map |
|
|
|
map[self._fileno] = self |
|
|
@ -596,8 +555,6 @@ class dispatcher: |
|
|
|
|
|
|
|
def del_channel(self, map=None): |
|
|
|
"""Delete a channel""" |
|
|
|
# pylint: disable=redefined-builtin |
|
|
|
|
|
|
|
fd = self._fileno |
|
|
|
if map is None: |
|
|
|
map = self._map |
|
|
@ -605,12 +562,14 @@ class dispatcher: |
|
|
|
del map[fd] |
|
|
|
if self._fileno: |
|
|
|
try: |
|
|
|
kqueue_poller.pollster.control([select.kevent(fd, select.KQ_FILTER_READ, select.KQ_EV_DELETE)], 0) |
|
|
|
except (AttributeError, KeyError, TypeError, IOError, OSError): |
|
|
|
kqueue_poller.pollster.control([select.kevent( |
|
|
|
fd, select.KQ_FILTER_READ, select.KQ_EV_DELETE)], 0) |
|
|
|
except(AttributeError, KeyError, TypeError, IOError, OSError): |
|
|
|
pass |
|
|
|
try: |
|
|
|
kqueue_poller.pollster.control([select.kevent(fd, select.KQ_FILTER_WRITE, select.KQ_EV_DELETE)], 0) |
|
|
|
except (AttributeError, KeyError, TypeError, IOError, OSError): |
|
|
|
kqueue_poller.pollster.control([select.kevent( |
|
|
|
fd, select.KQ_FILTER_WRITE, select.KQ_EV_DELETE)], 0) |
|
|
|
except(AttributeError, KeyError, TypeError, IOError, OSError): |
|
|
|
pass |
|
|
|
try: |
|
|
|
epoll_poller.pollster.unregister(fd) |
|
|
@ -627,8 +586,10 @@ class dispatcher: |
|
|
|
self.poller_filter = 0 |
|
|
|
self.poller_registered = False |
|
|
|
|
|
|
|
def create_socket(self, family=socket.AF_INET, socket_type=socket.SOCK_STREAM): |
|
|
|
def create_socket( |
|
|
|
self, family=socket.AF_INET, socket_type=socket.SOCK_STREAM): |
|
|
|
"""Create a socket""" |
|
|
|
# pylint: disable=attribute-defined-outside-init |
|
|
|
self.family_and_type = family, socket_type |
|
|
|
sock = socket.socket(family, socket_type) |
|
|
|
sock.setblocking(0) |
|
|
@ -636,20 +597,16 @@ class dispatcher: |
|
|
|
|
|
|
|
def set_socket(self, sock, map=None): |
|
|
|
"""Set socket""" |
|
|
|
# pylint: disable=redefined-builtin |
|
|
|
|
|
|
|
self.socket = sock |
|
|
|
self._fileno = sock.fileno() |
|
|
|
self.add_channel(map) |
|
|
|
|
|
|
|
def set_reuse_addr(self): |
|
|
|
"""try to re-use a server port if possible""" |
|
|
|
|
|
|
|
try: |
|
|
|
self.socket.setsockopt( |
|
|
|
socket.SOL_SOCKET, socket.SO_REUSEADDR, |
|
|
|
self.socket.getsockopt(socket.SOL_SOCKET, |
|
|
|
socket.SO_REUSEADDR) | 1 |
|
|
|
socket.SOL_SOCKET, socket.SO_REUSEADDR, self.socket.getsockopt( |
|
|
|
socket.SOL_SOCKET, socket.SO_REUSEADDR) | 1 |
|
|
|
) |
|
|
|
except socket.error: |
|
|
|
pass |
|
|
@ -704,13 +661,16 @@ class dispatcher: |
|
|
|
raise socket.error(err, errorcode[err]) |
|
|
|
|
|
|
|
def accept(self): |
|
|
|
"""Accept incoming connections. Returns either an address pair or None.""" |
|
|
|
"""Accept incoming connections. |
|
|
|
Returns either an address pair or None.""" |
|
|
|
try: |
|
|
|
conn, addr = self.socket.accept() |
|
|
|
except TypeError: |
|
|
|
return None |
|
|
|
except socket.error as why: |
|
|
|
if why.args[0] in (EWOULDBLOCK, WSAEWOULDBLOCK, ECONNABORTED, EAGAIN, ENOTCONN): |
|
|
|
if why.args[0] in ( |
|
|
|
EWOULDBLOCK, WSAEWOULDBLOCK, ECONNABORTED, |
|
|
|
EAGAIN, ENOTCONN): |
|
|
|
return None |
|
|
|
else: |
|
|
|
raise |
|
|
@ -769,11 +729,12 @@ class dispatcher: |
|
|
|
try: |
|
|
|
retattr = getattr(self.socket, attr) |
|
|
|
except AttributeError: |
|
|
|
raise AttributeError("%s instance has no attribute '%s'" |
|
|
|
% (self.__class__.__name__, attr)) |
|
|
|
raise AttributeError( |
|
|
|
"%s instance has no attribute '%s'" |
|
|
|
% (self.__class__.__name__, attr)) |
|
|
|
else: |
|
|
|
msg = "%(me)s.%(attr)s is deprecated; use %(me)s.socket.%(attr)s " \ |
|
|
|
"instead" % {'me': self.__class__.__name__, 'attr': attr} |
|
|
|
msg = "%(me)s.%(attr)s is deprecated; use %(me)s.socket.%(attr)s"\ |
|
|
|
" instead" % {'me': self.__class__.__name__, 'attr': attr} |
|
|
|
warnings.warn(msg, DeprecationWarning, stacklevel=2) |
|
|
|
return retattr |
|
|
|
|
|
|
@ -855,13 +816,8 @@ class dispatcher: |
|
|
|
|
|
|
|
self.log_info( |
|
|
|
'uncaptured python exception, closing channel %s (%s:%s %s)' % ( |
|
|
|
self_repr, |
|
|
|
t, |
|
|
|
v, |
|
|
|
tbinfo |
|
|
|
), |
|
|
|
'error' |
|
|
|
) |
|
|
|
self_repr, t, v, tbinfo), |
|
|
|
'error') |
|
|
|
self.handle_close() |
|
|
|
|
|
|
|
def handle_accept(self): |
|
|
@ -902,11 +858,8 @@ class dispatcher_with_send(dispatcher): |
|
|
|
adds simple buffered output capability, useful for simple clients. |
|
|
|
[for more sophisticated usage use asynchat.async_chat] |
|
|
|
""" |
|
|
|
# pylint: disable=redefined-builtin |
|
|
|
|
|
|
|
def __init__(self, sock=None, map=None): |
|
|
|
# pylint: disable=redefined-builtin |
|
|
|
|
|
|
|
dispatcher.__init__(self, sock, map) |
|
|
|
self.out_buffer = b'' |
|
|
|
|
|
|
@ -941,7 +894,8 @@ def compact_traceback(): |
|
|
|
"""Return a compact traceback""" |
|
|
|
t, v, tb = sys.exc_info() |
|
|
|
tbinfo = [] |
|
|
|
if not tb: # Must have a traceback |
|
|
|
# Must have a traceback |
|
|
|
if not tb: |
|
|
|
raise AssertionError("traceback does not exist") |
|
|
|
while tb: |
|
|
|
tbinfo.append(( |
|
|
@ -961,7 +915,6 @@ def compact_traceback(): |
|
|
|
|
|
|
|
def close_all(map=None, ignore_all=False): |
|
|
|
"""Close all connections""" |
|
|
|
# pylint: disable=redefined-builtin |
|
|
|
|
|
|
|
if map is None: |
|
|
|
map = socket_map |
|
|
@ -998,13 +951,13 @@ def close_all(map=None, ignore_all=False): |
|
|
|
if os.name == 'posix': |
|
|
|
import fcntl |
|
|
|
|
|
|
|
class file_wrapper: |
|
|
|
class file_wrapper: # pylint: disable=old-style-class |
|
|
|
""" |
|
|
|
Here we override just enough to make a file look like a socket for the purposes of asyncore. |
|
|
|
Here we override just enough to make a file look |
|
|
|
like a socket for the purposes of asyncore. |
|
|
|
|
|
|
|
The passed fd is automatically os.dup()'d |
|
|
|
""" |
|
|
|
# pylint: disable=old-style-class |
|
|
|
|
|
|
|
def __init__(self, fd): |
|
|
|
self.fd = os.dup(fd) |
|
|
@ -1019,12 +972,11 @@ if os.name == 'posix': |
|
|
|
|
|
|
|
def getsockopt(self, level, optname, buflen=None): |
|
|
|
"""Fake getsockopt()""" |
|
|
|
if (level == socket.SOL_SOCKET and |
|
|
|
optname == socket.SO_ERROR and |
|
|
|
if (level == socket.SOL_SOCKET and optname == socket.SO_ERROR and |
|
|
|
not buflen): |
|
|
|
return 0 |
|
|
|
raise NotImplementedError("Only asyncore specific behaviour " |
|
|
|
"implemented.") |
|
|
|
raise NotImplementedError( |
|
|
|
"Only asyncore specific behaviour implemented.") |
|
|
|
|
|
|
|
read = recv |
|
|
|
write = send |
|
|
@ -1041,8 +993,6 @@ if os.name == 'posix': |
|
|
|
"""A dispatcher for file_wrapper objects""" |
|
|
|
|
|
|
|
def __init__(self, fd, map=None): |
|
|
|
# pylint: disable=redefined-builtin |
|
|
|
|
|
|
|
dispatcher.__init__(self, None, map) |
|
|
|
self.connected = True |
|
|
|
try: |
|
|
|