Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1d29cc5

Browse files
committed
Issue #21040: socketserver: Use the selectors module.
1 parent e3fb80f commit 1d29cc5

3 files changed

Lines changed: 59 additions & 75 deletions

File tree

Doc/library/socketserver.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ the request handler class :meth:`handle` method.
113113
Another approach to handling multiple simultaneous requests in an environment
114114
that supports neither threads nor :func:`~os.fork` (or where these are too
115115
expensive or inappropriate for the service) is to maintain an explicit table of
116-
partially finished requests and to use :func:`~select.select` to decide which
116+
partially finished requests and to use :mod:`selectors` to decide which
117117
request to work on next (or whether to handle a new incoming request). This is
118118
particularly important for stream services where each client can potentially be
119119
connected for a long time (if threads or subprocesses cannot be used). See
@@ -136,7 +136,7 @@ Server Objects
136136
.. method:: BaseServer.fileno()
137137

138138
Return an integer file descriptor for the socket on which the server is
139-
listening. This function is most commonly passed to :func:`select.select`, to
139+
listening. This function is most commonly passed to :mod:`selectors`, to
140140
allow monitoring multiple servers in the same process.
141141

142142

Lib/socketserver.py

Lines changed: 57 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ class will essentially render the service "deaf" while one request is
9494
Another approach to handling multiple simultaneous requests in an
9595
environment that supports neither threads nor fork (or where these are
9696
too expensive or inappropriate for the service) is to maintain an
97-
explicit table of partially finished requests and to use select() to
97+
explicit table of partially finished requests and to use a selector to
9898
decide which request to work on next (or whether to handle a new
9999
incoming request). This is particularly important for stream services
100100
where each client can potentially be connected for a long time (if
@@ -104,7 +104,6 @@ class will essentially render the service "deaf" while one request is
104104
- Standard classes for Sun RPC (which uses either UDP or TCP)
105105
- Standard mix-in classes to implement various authentication
106106
and encryption schemes
107-
- Standard framework for select-based multiplexing
108107
109108
XXX Open problems:
110109
- What to do with out-of-band data?
@@ -130,13 +129,17 @@ class will essentially render the service "deaf" while one request is
130129

131130

132131
import socket
133-
import select
132+
import selectors
134133
import os
135134
import errno
136135
try:
137136
import threading
138137
except ImportError:
139138
import dummy_threading as threading
139+
try:
140+
from time import monotonic as time
141+
except ImportError:
142+
from time import time as time
140143

141144
__all__ = ["TCPServer","UDPServer","ForkingUDPServer","ForkingTCPServer",
142145
"ThreadingUDPServer","ThreadingTCPServer","BaseRequestHandler",
@@ -147,14 +150,13 @@ class will essentially render the service "deaf" while one request is
147150
"ThreadingUnixStreamServer",
148151
"ThreadingUnixDatagramServer"])
149152

150-
def _eintr_retry(func, *args):
151-
"""restart a system call interrupted by EINTR"""
152-
while True:
153-
try:
154-
return func(*args)
155-
except OSError as e:
156-
if e.errno != errno.EINTR:
157-
raise
153+
# poll/select have the advantage of not requiring any extra file descriptor,
154+
# contrarily to epoll/kqueue (also, they require a single syscall).
155+
if hasattr(selectors, 'PollSelector'):
156+
_ServerSelector = selectors.PollSelector
157+
else:
158+
_ServerSelector = selectors.SelectSelector
159+
158160

159161
class BaseServer:
160162

@@ -166,7 +168,7 @@ class BaseServer:
166168
- serve_forever(poll_interval=0.5)
167169
- shutdown()
168170
- handle_request() # if you do not use serve_forever()
169-
- fileno() -> int # for select()
171+
- fileno() -> int # for selector
170172
171173
Methods that may be overridden:
172174
@@ -227,17 +229,19 @@ def serve_forever(self, poll_interval=0.5):
227229
"""
228230
self.__is_shut_down.clear()
229231
try:
230-
while not self.__shutdown_request:
231-
# XXX: Consider using another file descriptor or
232-
# connecting to the socket to wake this up instead of
233-
# polling. Polling reduces our responsiveness to a
234-
# shutdown request and wastes cpu at all other times.
235-
r, w, e = _eintr_retry(select.select, [self], [], [],
236-
poll_interval)
237-
if self in r:
238-
self._handle_request_noblock()
239-
240-
self.service_actions()
232+
# XXX: Consider using another file descriptor or connecting to the
233+
# socket to wake this up instead of polling. Polling reduces our
234+
# responsiveness to a shutdown request and wastes cpu at all other
235+
# times.
236+
with _ServerSelector() as selector:
237+
selector.register(self, selectors.EVENT_READ)
238+
239+
while not self.__shutdown_request:
240+
ready = selector.select(poll_interval)
241+
if ready:
242+
self._handle_request_noblock()
243+
244+
self.service_actions()
241245
finally:
242246
self.__shutdown_request = False
243247
self.__is_shut_down.set()
@@ -260,16 +264,16 @@ def service_actions(self):
260264
"""
261265
pass
262266

263-
# The distinction between handling, getting, processing and
264-
# finishing a request is fairly arbitrary. Remember:
267+
# The distinction between handling, getting, processing and finishing a
268+
# request is fairly arbitrary. Remember:
265269
#
266-
# - handle_request() is the top-level call. It calls
267-
# select, get_request(), verify_request() and process_request()
270+
# - handle_request() is the top-level call. It calls selector.select(),
271+
# get_request(), verify_request() and process_request()
268272
# - get_request() is different for stream or datagram sockets
269-
# - process_request() is the place that may fork a new process
270-
# or create a new thread to finish the request
271-
# - finish_request() instantiates the request handler class;
272-
# this constructor will handle the request all by itself
273+
# - process_request() is the place that may fork a new process or create a
274+
# new thread to finish the request
275+
# - finish_request() instantiates the request handler class; this
276+
# constructor will handle the request all by itself
273277

274278
def handle_request(self):
275279
"""Handle one request, possibly blocking.
@@ -283,18 +287,30 @@ def handle_request(self):
283287
timeout = self.timeout
284288
elif self.timeout is not None:
285289
timeout = min(timeout, self.timeout)
286-
fd_sets = _eintr_retry(select.select, [self], [], [], timeout)
287-
if not fd_sets[0]:
288-
self.handle_timeout()
289-
return
290-
self._handle_request_noblock()
290+
if timeout is not None:
291+
deadline = time() + timeout
292+
293+
# Wait until a request arrives or the timeout expires - the loop is
294+
# necessary to accomodate early wakeups due to EINTR.
295+
with _ServerSelector() as selector:
296+
selector.register(self, selectors.EVENT_READ)
297+
298+
while True:
299+
ready = selector.select(timeout)
300+
if ready:
301+
return self._handle_request_noblock()
302+
else:
303+
if timeout is not None:
304+
timeout = deadline - time()
305+
if timeout < 0:
306+
return self.handle_timeout()
291307

292308
def _handle_request_noblock(self):
293309
"""Handle one request, without blocking.
294310
295-
I assume that select.select has returned that the socket is
296-
readable before this function was called, so there should be
297-
no risk of blocking in get_request().
311+
I assume that selector.select() has returned that the socket is
312+
readable before this function was called, so there should be no risk of
313+
blocking in get_request().
298314
"""
299315
try:
300316
request, client_address = self.get_request()
@@ -377,7 +393,7 @@ class TCPServer(BaseServer):
377393
- serve_forever(poll_interval=0.5)
378394
- shutdown()
379395
- handle_request() # if you don't use serve_forever()
380-
- fileno() -> int # for select()
396+
- fileno() -> int # for selector
381397
382398
Methods that may be overridden:
383399
@@ -459,7 +475,7 @@ def server_close(self):
459475
def fileno(self):
460476
"""Return socket file number.
461477
462-
Interface required by select().
478+
Interface required by selector.
463479
464480
"""
465481
return self.socket.fileno()

Lib/test/test_socketserver.py

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -222,38 +222,6 @@ def test_ForkingUDPServer(self):
222222
socketserver.DatagramRequestHandler,
223223
self.dgram_examine)
224224

225-
@contextlib.contextmanager
226-
def mocked_select_module(self):
227-
"""Mocks the select.select() call to raise EINTR for first call"""
228-
old_select = select.select
229-
230-
class MockSelect:
231-
def __init__(self):
232-
self.called = 0
233-
234-
def __call__(self, *args):
235-
self.called += 1
236-
if self.called == 1:
237-
# raise the exception on first call
238-
raise OSError(errno.EINTR, os.strerror(errno.EINTR))
239-
else:
240-
# Return real select value for consecutive calls
241-
return old_select(*args)
242-
243-
select.select = MockSelect()
244-
try:
245-
yield select.select
246-
finally:
247-
select.select = old_select
248-
249-
def test_InterruptServerSelectCall(self):
250-
with self.mocked_select_module() as mock_select:
251-
pid = self.run_server(socketserver.TCPServer,
252-
socketserver.StreamRequestHandler,
253-
self.stream_examine)
254-
# Make sure select was called again:
255-
self.assertGreater(mock_select.called, 1)
256-
257225
# Alas, on Linux (at least) recvfrom() doesn't return a meaningful
258226
# client address so this cannot work:
259227

0 commit comments

Comments
 (0)