# Id: asyncore.py,v 2.51 2000/09/07 22:29:26 rushing Exp
# Author: Sam Rushing <rushing@nightmare.com>
# ======================================================================
# Copyright 1996 by Sam Rushing
# 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
# 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.
from errno
import EALREADY
, EINPROGRESS
, EWOULDBLOCK
, ECONNRESET
, \
ENOTCONN
, ESHUTDOWN
, EINTR
, EISCONN
, errorcode
class ExitNow(exceptions
.Exception):
def readwrite(obj
, flags
):
if flags
& (select
.POLLIN | select
.POLLPRI
):
if flags
& select
.POLLOUT
:
if flags
& (select
.POLLERR | select
.POLLHUP | select
.POLLNVAL
):
def poll(timeout
=0.0, map=None):
for fd
, obj
in map.items():
r
, w
, e
= select
.select(r
, w
, e
, timeout
)
except select
.error
, err
:
def poll2(timeout
=0.0, map=None):
# Use the poll() support added to the select module in Python 2.0
# timeout is in milliseconds
timeout
= int(timeout
*1000)
for fd
, obj
in map.items():
flags |
= select
.POLLIN | select
.POLLPRI
# Only check for exceptions if object was either readable
flags |
= select
.POLLERR | select
.POLLHUP | select
.POLLNVAL
pollster
.register(fd
, flags
)
r
= pollster
.poll(timeout
)
except select
.error
, err
:
poll3
= poll2
# Alias for backward compatibility
def loop(timeout
=30.0, use_poll
=False, map=None, count
=None):
if use_poll
and hasattr(select
, 'poll'):
def __init__(self
, sock
=None, map=None):
self
.set_socket(sock
, map)
# I think it should inherit this anyway
self
.socket
.setblocking(0)
# XXX Does the constructor require that the socket passed
self
.addr
= sock
.getpeername()
status
= [self
.__class
__.__module
__+"."+self
.__class
__.__name
__]
if self
.accepting
and self
.addr
:
status
.append('listening')
status
.append('connected')
if self
.addr
is not None:
status
.append('%s:%d' % self
.addr
)
status
.append(repr(self
.addr
))
return '<%s at %#x>' % (' '.join(status
), id(self
))
def add_channel(self
, map=None):
#self.log_info('adding channel %s' % self)
def del_channel(self
, map=None):
#self.log_info('closing channel %d:%s' % (fd, self))
def create_socket(self
, family
, type):
self
.family_and_type
= family
, type
self
.socket
= socket
.socket(family
, type)
self
.socket
.setblocking(0)
self
._fileno
= self
.socket
.fileno()
def set_socket(self
, sock
, map=None):
## self.__dict__['socket'] = sock
self
._fileno
= sock
.fileno()
def set_reuse_addr(self
):
# try to re-use a server port if possible
socket
.SOL_SOCKET
, socket
.SO_REUSEADDR
,
self
.socket
.getsockopt(socket
.SOL_SOCKET
,
# ==================================================
# predicates for select()
# these are used as filters for the lists of sockets
# ==================================================
# ==================================================
# ==================================================
if os
.name
== 'nt' and num
> 5:
return self
.socket
.listen(num
)
return self
.socket
.bind(addr
)
def connect(self
, address
):
err
= self
.socket
.connect_ex(address
)
# XXX Should interpret Winsock return values
if err
in (EINPROGRESS
, EALREADY
, EWOULDBLOCK
):
raise socket
.error
, (err
, errorcode
[err
])
# XXX can return either an address pair or None
conn
, addr
= self
.socket
.accept()
except socket
.error
, why
:
if why
[0] == EWOULDBLOCK
:
result
= self
.socket
.send(data
)
except socket
.error
, why
:
if why
[0] == EWOULDBLOCK
:
def recv(self
, buffer_size
):
data
= self
.socket
.recv(buffer_size
)
# a closed connection is indicated by signaling
# a read condition, and having recv() return 0.
except socket
.error
, why
:
# winsock sometimes throws ENOTCONN
if why
[0] in [ECONNRESET
, ENOTCONN
, ESHUTDOWN
]:
# cheap inheritance, used to pass all other attribute
# references to the underlying socket object.
def __getattr__(self
, attr
):
return getattr(self
.socket
, attr
)
# log and log_info may be overridden to provide more sophisticated
# logging and warning methods. In general, log is for 'hit' logging
# and 'log_info' is for informational, warning and error logging.
sys
.stderr
.write('log: %s\n' % str(message
))
def log_info(self
, message
, type='info'):
if __debug__
or type != 'info':
print '%s: %s' % (type, message
)
def handle_read_event(self
):
# for an accepting socket, getting a read implies
def handle_write_event(self
):
# getting a write implies that we are connected
def handle_expt_event(self
):
nil
, t
, v
, tbinfo
= compact_traceback()
# sometimes a user repr method will crash.
self_repr
= '<__repr__(self) failed for object at %0x>' % id(self
)
'uncaptured python exception, closing channel %s (%s:%s %s)' % (
self
.log_info('unhandled exception', 'warning')
self
.log_info('unhandled read event', 'warning')
self
.log_info('unhandled write event', 'warning')
def handle_connect(self
):
self
.log_info('unhandled connect event', 'warning')
self
.log_info('unhandled accept event', 'warning')
self
.log_info('unhandled close event', 'warning')
# ---------------------------------------------------------------------------
# adds simple buffered output capability, useful for simple clients.
# [for more sophisticated usage use asynchat.async_chat]
# ---------------------------------------------------------------------------
class dispatcher_with_send(dispatcher
):
def __init__(self
, sock
=None, map=None):
dispatcher
.__init
__(self
, sock
, map)
num_sent
= dispatcher
.send(self
, self
.out_buffer
[:512])
self
.out_buffer
= self
.out_buffer
[num_sent
:]
return (not self
.connected
) or len(self
.out_buffer
)
self
.log_info('sending %s' % repr(data
))
self
.out_buffer
= self
.out_buffer
+ data
# ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------
t
, v
, tb
= sys
.exc_info()
assert tb
# Must have a traceback
tb
.tb_frame
.f_code
.co_filename
,
tb
.tb_frame
.f_code
.co_name
,
file, function
, line
= tbinfo
[-1]
info
= ' '.join(['[%s|%s|%s]' % x
for x
in tbinfo
])
return (file, function
, line
), t
, v
, info
# After a little research (reading man pages on various unixen, and
# digging through the linux kernel), I've determined that select()
# isn't meant for doing asynchronous file i/o.
# Heartening, though - reading linux/mm/filemap.c shows that linux
# supports asynchronous read-ahead. So _MOST_ of the time, the data
# will be sitting in memory for us already when we go to read it.
# What other OS's (besides NT) support async file i/o? [VMS?]
# Regardless, this is useful for pipes, and stdin/stdout...
# here we override just enough to make a file
# look like a socket for the purposes of asyncore.
return os
.read(self
.fd
, *args
)
return os
.write(self
.fd
, *args
)
class file_dispatcher(dispatcher
):
def __init__(self
, fd
, map=None):
dispatcher
.__init
__(self
, None, map)
# set it to non-blocking mode
flags
= fcntl
.fcntl(fd
, fcntl
.F_GETFL
, 0)
flags
= flags | os
.O_NONBLOCK
fcntl
.fcntl(fd
, fcntl
.F_SETFL
, flags
)
self
.socket
= file_wrapper(fd
)