diff options
author | Charles-François Natali <cf.natali@gmail.com> | 2014-03-24 22:25:39 (GMT) |
---|---|---|
committer | Charles-François Natali <cf.natali@gmail.com> | 2014-03-24 22:25:39 (GMT) |
commit | 1d29cc5b6c72afc8e310f99e8245de78493553b9 (patch) | |
tree | 7e1c8a2087a5c43b527c8203a67f31c1dc3dd388 /Lib/socketserver.py | |
parent | e3fb80fb7608538618d3cf731ff39c74a0faa651 (diff) | |
download | cpython-1d29cc5b6c72afc8e310f99e8245de78493553b9.zip cpython-1d29cc5b6c72afc8e310f99e8245de78493553b9.tar.gz cpython-1d29cc5b6c72afc8e310f99e8245de78493553b9.tar.bz2 |
Issue #21040: socketserver: Use the selectors module.
Diffstat (limited to 'Lib/socketserver.py')
-rw-r--r-- | Lib/socketserver.py | 98 |
1 files changed, 57 insertions, 41 deletions
diff --git a/Lib/socketserver.py b/Lib/socketserver.py index 46ee7c5..7c85fbc 100644 --- a/Lib/socketserver.py +++ b/Lib/socketserver.py @@ -94,7 +94,7 @@ handle() method. Another approach to handling multiple simultaneous requests in an environment that supports neither threads nor fork (or where these are too expensive or inappropriate for the service) is to maintain an -explicit table of partially finished requests and to use select() to +explicit table of partially finished requests and to use a selector to decide which request to work on next (or whether to handle a new incoming request). This is particularly important for stream services where each client can potentially be connected for a long time (if @@ -104,7 +104,6 @@ Future work: - Standard classes for Sun RPC (which uses either UDP or TCP) - Standard mix-in classes to implement various authentication and encryption schemes -- Standard framework for select-based multiplexing XXX Open problems: - What to do with out-of-band data? @@ -130,13 +129,17 @@ __version__ = "0.4" import socket -import select +import selectors import os import errno try: import threading except ImportError: import dummy_threading as threading +try: + from time import monotonic as time +except ImportError: + from time import time as time __all__ = ["TCPServer","UDPServer","ForkingUDPServer","ForkingTCPServer", "ThreadingUDPServer","ThreadingTCPServer","BaseRequestHandler", @@ -147,14 +150,13 @@ if hasattr(socket, "AF_UNIX"): "ThreadingUnixStreamServer", "ThreadingUnixDatagramServer"]) -def _eintr_retry(func, *args): - """restart a system call interrupted by EINTR""" - while True: - try: - return func(*args) - except OSError as e: - if e.errno != errno.EINTR: - raise +# poll/select have the advantage of not requiring any extra file descriptor, +# contrarily to epoll/kqueue (also, they require a single syscall). +if hasattr(selectors, 'PollSelector'): + _ServerSelector = selectors.PollSelector +else: + _ServerSelector = selectors.SelectSelector + class BaseServer: @@ -166,7 +168,7 @@ class BaseServer: - serve_forever(poll_interval=0.5) - shutdown() - handle_request() # if you do not use serve_forever() - - fileno() -> int # for select() + - fileno() -> int # for selector Methods that may be overridden: @@ -227,17 +229,19 @@ class BaseServer: """ self.__is_shut_down.clear() try: - while not self.__shutdown_request: - # XXX: Consider using another file descriptor or - # connecting to the socket to wake this up instead of - # polling. Polling reduces our responsiveness to a - # shutdown request and wastes cpu at all other times. - r, w, e = _eintr_retry(select.select, [self], [], [], - poll_interval) - if self in r: - self._handle_request_noblock() - - self.service_actions() + # XXX: Consider using another file descriptor or connecting to the + # socket to wake this up instead of polling. Polling reduces our + # responsiveness to a shutdown request and wastes cpu at all other + # times. + with _ServerSelector() as selector: + selector.register(self, selectors.EVENT_READ) + + while not self.__shutdown_request: + ready = selector.select(poll_interval) + if ready: + self._handle_request_noblock() + + self.service_actions() finally: self.__shutdown_request = False self.__is_shut_down.set() @@ -260,16 +264,16 @@ class BaseServer: """ pass - # The distinction between handling, getting, processing and - # finishing a request is fairly arbitrary. Remember: + # The distinction between handling, getting, processing and finishing a + # request is fairly arbitrary. Remember: # - # - handle_request() is the top-level call. It calls - # select, get_request(), verify_request() and process_request() + # - handle_request() is the top-level call. It calls selector.select(), + # get_request(), verify_request() and process_request() # - get_request() is different for stream or datagram sockets - # - process_request() is the place that may fork a new process - # or create a new thread to finish the request - # - finish_request() instantiates the request handler class; - # this constructor will handle the request all by itself + # - process_request() is the place that may fork a new process or create a + # new thread to finish the request + # - finish_request() instantiates the request handler class; this + # constructor will handle the request all by itself def handle_request(self): """Handle one request, possibly blocking. @@ -283,18 +287,30 @@ class BaseServer: timeout = self.timeout elif self.timeout is not None: timeout = min(timeout, self.timeout) - fd_sets = _eintr_retry(select.select, [self], [], [], timeout) - if not fd_sets[0]: - self.handle_timeout() - return - self._handle_request_noblock() + if timeout is not None: + deadline = time() + timeout + + # Wait until a request arrives or the timeout expires - the loop is + # necessary to accomodate early wakeups due to EINTR. + with _ServerSelector() as selector: + selector.register(self, selectors.EVENT_READ) + + while True: + ready = selector.select(timeout) + if ready: + return self._handle_request_noblock() + else: + if timeout is not None: + timeout = deadline - time() + if timeout < 0: + return self.handle_timeout() def _handle_request_noblock(self): """Handle one request, without blocking. - I assume that select.select has returned that the socket is - readable before this function was called, so there should be - no risk of blocking in get_request(). + I assume that selector.select() has returned that the socket is + readable before this function was called, so there should be no risk of + blocking in get_request(). """ try: request, client_address = self.get_request() @@ -377,7 +393,7 @@ class TCPServer(BaseServer): - serve_forever(poll_interval=0.5) - shutdown() - handle_request() # if you don't use serve_forever() - - fileno() -> int # for select() + - fileno() -> int # for selector Methods that may be overridden: @@ -459,7 +475,7 @@ class TCPServer(BaseServer): def fileno(self): """Return socket file number. - Interface required by select(). + Interface required by selector. """ return self.socket.fileno() |