PageRenderTime 74ms CodeModel.GetById 20ms app.highlight 48ms RepoModel.GetById 1ms app.codeStats 1ms

/Lib/asyncore.py

http://unladen-swallow.googlecode.com/
Python | 618 lines | 589 code | 2 blank | 27 comment | 1 complexity | 658962db16e38f24a8884f5e6e2deaf4 MD5 | raw file
  1# -*- Mode: Python -*-
  2#   Id: asyncore.py,v 2.51 2000/09/07 22:29:26 rushing Exp
  3#   Author: Sam Rushing <rushing@nightmare.com>
  4
  5# ======================================================================
  6# Copyright 1996 by Sam Rushing
  7#
  8#                         All Rights Reserved
  9#
 10# Permission to use, copy, modify, and distribute this software and
 11# its documentation for any purpose and without fee is hereby
 12# granted, provided that the above copyright notice appear in all
 13# copies and that both that copyright notice and this permission
 14# notice appear in supporting documentation, and that the name of Sam
 15# Rushing not be used in advertising or publicity pertaining to
 16# distribution of the software without specific, written prior
 17# permission.
 18#
 19# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 20# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
 21# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 22# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 23# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 24# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 25# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 26# ======================================================================
 27
 28"""Basic infrastructure for asynchronous socket service clients and servers.
 29
 30There are only two ways to have a program on a single processor do "more
 31than one thing at a time".  Multi-threaded programming is the simplest and
 32most popular way to do it, but there is another very different technique,
 33that lets you have nearly all the advantages of multi-threading, without
 34actually using multiple threads. it's really only practical if your program
 35is largely I/O bound. If your program is CPU bound, then pre-emptive
 36scheduled threads are probably what you really need. Network servers are
 37rarely CPU-bound, however.
 38
 39If your operating system supports the select() system call in its I/O
 40library (and nearly all do), then you can use it to juggle multiple
 41communication channels at once; doing other work while your I/O is taking
 42place in the "background."  Although this strategy can seem strange and
 43complex, especially at first, it is in many ways easier to understand and
 44control than multi-threaded programming. The module documented here solves
 45many of the difficult problems for you, making the task of building
 46sophisticated high-performance network servers and clients a snap.
 47"""
 48
 49import select
 50import socket
 51import sys
 52import time
 53
 54import os
 55from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, \
 56     ENOTCONN, ESHUTDOWN, EINTR, EISCONN, EBADF, ECONNABORTED, errorcode
 57
 58try:
 59    socket_map
 60except NameError:
 61    socket_map = {}
 62
 63def _strerror(err):
 64    res = os.strerror(err)
 65    if res == 'Unknown error':
 66        res = errorcode[err]
 67    return res
 68
 69class ExitNow(Exception):
 70    pass
 71
 72_reraised_exceptions = (ExitNow, KeyboardInterrupt, SystemExit)
 73
 74def read(obj):
 75    try:
 76        obj.handle_read_event()
 77    except _reraised_exceptions:
 78        raise
 79    except:
 80        obj.handle_error()
 81
 82def write(obj):
 83    try:
 84        obj.handle_write_event()
 85    except _reraised_exceptions:
 86        raise
 87    except:
 88        obj.handle_error()
 89
 90def _exception(obj):
 91    try:
 92        obj.handle_expt_event()
 93    except _reraised_exceptions:
 94        raise
 95    except:
 96        obj.handle_error()
 97
 98def readwrite(obj, flags):
 99    try:
100        if flags & select.POLLIN:
101            obj.handle_read_event()
102        if flags & select.POLLOUT:
103            obj.handle_write_event()
104        if flags & (select.POLLHUP | select.POLLERR | select.POLLNVAL):
105            obj.handle_close()
106        if flags & select.POLLPRI:
107            obj.handle_expt_event()
108    except _reraised_exceptions:
109        raise
110    except:
111        obj.handle_error()
112
113def poll(timeout=0.0, map=None):
114    if map is None:
115        map = socket_map
116    if map:
117        r = []; w = []; e = []
118        for fd, obj in map.items():
119            is_r = obj.readable()
120            is_w = obj.writable()
121            if is_r:
122                r.append(fd)
123            if is_w:
124                w.append(fd)
125            if is_r or is_w:
126                e.append(fd)
127        if [] == r == w == e:
128            time.sleep(timeout)
129            return
130
131        try:
132            r, w, e = select.select(r, w, e, timeout)
133        except select.error, err:
134            if err.args[0] != EINTR:
135                raise
136            else:
137                return
138
139        for fd in r:
140            obj = map.get(fd)
141            if obj is None:
142                continue
143            read(obj)
144
145        for fd in w:
146            obj = map.get(fd)
147            if obj is None:
148                continue
149            write(obj)
150
151        for fd in e:
152            obj = map.get(fd)
153            if obj is None:
154                continue
155            _exception(obj)
156
157def poll2(timeout=0.0, map=None):
158    # Use the poll() support added to the select module in Python 2.0
159    if map is None:
160        map = socket_map
161    if timeout is not None:
162        # timeout is in milliseconds
163        timeout = int(timeout*1000)
164    pollster = select.poll()
165    if map:
166        for fd, obj in map.items():
167            flags = 0
168            if obj.readable():
169                flags |= select.POLLIN | select.POLLPRI
170            if obj.writable():
171                flags |= select.POLLOUT
172            if flags:
173                # Only check for exceptions if object was either readable
174                # or writable.
175                flags |= select.POLLERR | select.POLLHUP | select.POLLNVAL
176                pollster.register(fd, flags)
177        try:
178            r = pollster.poll(timeout)
179        except select.error, err:
180            if err.args[0] != EINTR:
181                raise
182            r = []
183        for fd, flags in r:
184            obj = map.get(fd)
185            if obj is None:
186                continue
187            readwrite(obj, flags)
188
189poll3 = poll2                           # Alias for backward compatibility
190
191def loop(timeout=30.0, use_poll=False, map=None, count=None):
192    if map is None:
193        map = socket_map
194
195    if use_poll and hasattr(select, 'poll'):
196        poll_fun = poll2
197    else:
198        poll_fun = poll
199
200    if count is None:
201        while map:
202            poll_fun(timeout, map)
203
204    else:
205        while map and count > 0:
206            poll_fun(timeout, map)
207            count = count - 1
208
209class dispatcher:
210
211    debug = False
212    connected = False
213    accepting = False
214    closing = False
215    addr = None
216    ignore_log_types = frozenset(['warning'])
217
218    def __init__(self, sock=None, map=None):
219        if map is None:
220            self._map = socket_map
221        else:
222            self._map = map
223
224        self._fileno = None
225
226        if sock:
227            # Set to nonblocking just to make sure for cases where we
228            # get a socket from a blocking source.
229            sock.setblocking(0)
230            self.set_socket(sock, map)
231            self.connected = True
232            # The constructor no longer requires that the socket
233            # passed be connected.
234            try:
235                self.addr = sock.getpeername()
236            except socket.error, err:
237                if err.args[0] == ENOTCONN:
238                    # To handle the case where we got an unconnected
239                    # socket.
240                    self.connected = False
241                else:
242                    # The socket is broken in some unknown way, alert
243                    # the user and remove it from the map (to prevent
244                    # polling of broken sockets).
245                    self.del_channel(map)
246                    raise
247        else:
248            self.socket = None
249
250    def __repr__(self):
251        status = [self.__class__.__module__+"."+self.__class__.__name__]
252        if self.accepting and self.addr:
253            status.append('listening')
254        elif self.connected:
255            status.append('connected')
256        if self.addr is not None:
257            try:
258                status.append('%s:%d' % self.addr)
259            except TypeError:
260                status.append(repr(self.addr))
261        return '<%s at %#x>' % (' '.join(status), id(self))
262
263    def add_channel(self, map=None):
264        #self.log_info('adding channel %s' % self)
265        if map is None:
266            map = self._map
267        map[self._fileno] = self
268
269    def del_channel(self, map=None):
270        fd = self._fileno
271        if map is None:
272            map = self._map
273        if fd in map:
274            #self.log_info('closing channel %d:%s' % (fd, self))
275            del map[fd]
276        self._fileno = None
277
278    def create_socket(self, family, type):
279        self.family_and_type = family, type
280        sock = socket.socket(family, type)
281        sock.setblocking(0)
282        self.set_socket(sock)
283
284    def set_socket(self, sock, map=None):
285        self.socket = sock
286##        self.__dict__['socket'] = sock
287        self._fileno = sock.fileno()
288        self.add_channel(map)
289
290    def set_reuse_addr(self):
291        # try to re-use a server port if possible
292        try:
293            self.socket.setsockopt(
294                socket.SOL_SOCKET, socket.SO_REUSEADDR,
295                self.socket.getsockopt(socket.SOL_SOCKET,
296                                       socket.SO_REUSEADDR) | 1
297                )
298        except socket.error:
299            pass
300
301    # ==================================================
302    # predicates for select()
303    # these are used as filters for the lists of sockets
304    # to pass to select().
305    # ==================================================
306
307    def readable(self):
308        return True
309
310    def writable(self):
311        return True
312
313    # ==================================================
314    # socket object methods.
315    # ==================================================
316
317    def listen(self, num):
318        self.accepting = True
319        if os.name == 'nt' and num > 5:
320            num = 5
321        return self.socket.listen(num)
322
323    def bind(self, addr):
324        self.addr = addr
325        return self.socket.bind(addr)
326
327    def connect(self, address):
328        self.connected = False
329        err = self.socket.connect_ex(address)
330        # XXX Should interpret Winsock return values
331        if err in (EINPROGRESS, EALREADY, EWOULDBLOCK):
332            return
333        if err in (0, EISCONN):
334            self.addr = address
335            self.handle_connect_event()
336        else:
337            raise socket.error(err, errorcode[err])
338
339    def accept(self):
340        # XXX can return either an address pair or None
341        try:
342            conn, addr = self.socket.accept()
343            return conn, addr
344        except socket.error, why:
345            if why.args[0] == EWOULDBLOCK:
346                pass
347            else:
348                raise
349
350    def send(self, data):
351        try:
352            result = self.socket.send(data)
353            return result
354        except socket.error, why:
355            if why.args[0] == EWOULDBLOCK:
356                return 0
357            elif why.args[0] in (ECONNRESET, ENOTCONN, ESHUTDOWN, ECONNABORTED):
358                self.handle_close()
359                return 0
360            else:
361                raise
362
363    def recv(self, buffer_size):
364        try:
365            data = self.socket.recv(buffer_size)
366            if not data:
367                # a closed connection is indicated by signaling
368                # a read condition, and having recv() return 0.
369                self.handle_close()
370                return ''
371            else:
372                return data
373        except socket.error, why:
374            # winsock sometimes throws ENOTCONN
375            if why.args[0] in [ECONNRESET, ENOTCONN, ESHUTDOWN, ECONNABORTED]:
376                self.handle_close()
377                return ''
378            else:
379                raise
380
381    def close(self):
382        self.connected = False
383        self.accepting = False
384        self.del_channel()
385        try:
386            self.socket.close()
387        except socket.error, why:
388            if why.args[0] not in (ENOTCONN, EBADF):
389                raise
390
391    # cheap inheritance, used to pass all other attribute
392    # references to the underlying socket object.
393    def __getattr__(self, attr):
394        return getattr(self.socket, attr)
395
396    # log and log_info may be overridden to provide more sophisticated
397    # logging and warning methods. In general, log is for 'hit' logging
398    # and 'log_info' is for informational, warning and error logging.
399
400    def log(self, message):
401        sys.stderr.write('log: %s\n' % str(message))
402
403    def log_info(self, message, type='info'):
404        if type not in self.ignore_log_types:
405            print '%s: %s' % (type, message)
406
407    def handle_read_event(self):
408        if self.accepting:
409            # accepting sockets are never connected, they "spawn" new
410            # sockets that are connected
411            self.handle_accept()
412        elif not self.connected:
413            self.handle_connect_event()
414            self.handle_read()
415        else:
416            self.handle_read()
417
418    def handle_connect_event(self):
419        self.connected = True
420        self.handle_connect()
421
422    def handle_write_event(self):
423        if self.accepting:
424            # Accepting sockets shouldn't get a write event.
425            # We will pretend it didn't happen.
426            return
427
428        if not self.connected:
429            #check for errors
430            err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
431            if err != 0:
432                raise socket.error(err, _strerror(err))
433
434            self.handle_connect_event()
435        self.handle_write()
436
437    def handle_expt_event(self):
438        # handle_expt_event() is called if there might be an error on the
439        # socket, or if there is OOB data
440        # check for the error condition first
441        err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
442        if err != 0:
443            # we can get here when select.select() says that there is an
444            # exceptional condition on the socket
445            # since there is an error, we'll go ahead and close the socket
446            # like we would in a subclassed handle_read() that received no
447            # data
448            self.handle_close()
449        else:
450            self.handle_expt()
451
452    def handle_error(self):
453        nil, t, v, tbinfo = compact_traceback()
454
455        # sometimes a user repr method will crash.
456        try:
457            self_repr = repr(self)
458        except:
459            self_repr = '<__repr__(self) failed for object at %0x>' % id(self)
460
461        self.log_info(
462            'uncaptured python exception, closing channel %s (%s:%s %s)' % (
463                self_repr,
464                t,
465                v,
466                tbinfo
467                ),
468            'error'
469            )
470        self.handle_close()
471
472    def handle_expt(self):
473        self.log_info('unhandled incoming priority event', 'warning')
474
475    def handle_read(self):
476        self.log_info('unhandled read event', 'warning')
477
478    def handle_write(self):
479        self.log_info('unhandled write event', 'warning')
480
481    def handle_connect(self):
482        self.log_info('unhandled connect event', 'warning')
483
484    def handle_accept(self):
485        self.log_info('unhandled accept event', 'warning')
486
487    def handle_close(self):
488        self.log_info('unhandled close event', 'warning')
489        self.close()
490
491# ---------------------------------------------------------------------------
492# adds simple buffered output capability, useful for simple clients.
493# [for more sophisticated usage use asynchat.async_chat]
494# ---------------------------------------------------------------------------
495
496class dispatcher_with_send(dispatcher):
497
498    def __init__(self, sock=None, map=None):
499        dispatcher.__init__(self, sock, map)
500        self.out_buffer = ''
501
502    def initiate_send(self):
503        num_sent = 0
504        num_sent = dispatcher.send(self, self.out_buffer[:512])
505        self.out_buffer = self.out_buffer[num_sent:]
506
507    def handle_write(self):
508        self.initiate_send()
509
510    def writable(self):
511        return (not self.connected) or len(self.out_buffer)
512
513    def send(self, data):
514        if self.debug:
515            self.log_info('sending %s' % repr(data))
516        self.out_buffer = self.out_buffer + data
517        self.initiate_send()
518
519# ---------------------------------------------------------------------------
520# used for debugging.
521# ---------------------------------------------------------------------------
522
523def compact_traceback():
524    t, v, tb = sys.exc_info()
525    tbinfo = []
526    if not tb: # Must have a traceback
527        raise AssertionError("traceback does not exist")
528    while tb:
529        tbinfo.append((
530            tb.tb_frame.f_code.co_filename,
531            tb.tb_frame.f_code.co_name,
532            str(tb.tb_lineno)
533            ))
534        tb = tb.tb_next
535
536    # just to be safe
537    del tb
538
539    file, function, line = tbinfo[-1]
540    info = ' '.join(['[%s|%s|%s]' % x for x in tbinfo])
541    return (file, function, line), t, v, info
542
543def close_all(map=None, ignore_all=False):
544    if map is None:
545        map = socket_map
546    for x in map.values():
547        try:
548            x.close()
549        except OSError, x:
550            if x.args[0] == EBADF:
551                pass
552            elif not ignore_all:
553                raise
554        except _reraised_exceptions:
555            raise
556        except:
557            if not ignore_all:
558                raise
559    map.clear()
560
561# Asynchronous File I/O:
562#
563# After a little research (reading man pages on various unixen, and
564# digging through the linux kernel), I've determined that select()
565# isn't meant for doing asynchronous file i/o.
566# Heartening, though - reading linux/mm/filemap.c shows that linux
567# supports asynchronous read-ahead.  So _MOST_ of the time, the data
568# will be sitting in memory for us already when we go to read it.
569#
570# What other OS's (besides NT) support async file i/o?  [VMS?]
571#
572# Regardless, this is useful for pipes, and stdin/stdout...
573
574if os.name == 'posix':
575    import fcntl
576
577    class file_wrapper:
578        # Here we override just enough to make a file
579        # look like a socket for the purposes of asyncore.
580        # The passed fd is automatically os.dup()'d
581
582        def __init__(self, fd):
583            self.fd = os.dup(fd)
584
585        def recv(self, *args):
586            return os.read(self.fd, *args)
587
588        def send(self, *args):
589            return os.write(self.fd, *args)
590
591        read = recv
592        write = send
593
594        def close(self):
595            os.close(self.fd)
596
597        def fileno(self):
598            return self.fd
599
600    class file_dispatcher(dispatcher):
601
602        def __init__(self, fd, map=None):
603            dispatcher.__init__(self, None, map)
604            self.connected = True
605            try:
606                fd = fd.fileno()
607            except AttributeError:
608                pass
609            self.set_file(fd)
610            # set it to non-blocking mode
611            flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0)
612            flags = flags | os.O_NONBLOCK
613            fcntl.fcntl(fd, fcntl.F_SETFL, flags)
614
615        def set_file(self, fd):
616            self.socket = file_wrapper(fd)
617            self._fileno = self.socket.fileno()
618            self.add_channel()