Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cpython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
cpython
Commits
1d29cc5b
Commit
1d29cc5b
authored
Mar 24, 2014
by
Charles-François Natali
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Issue #21040: socketserver: Use the selectors module.
parent
e3fb80fb
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
59 additions
and
75 deletions
+59
-75
Doc/library/socketserver.rst
Doc/library/socketserver.rst
+2
-2
Lib/socketserver.py
Lib/socketserver.py
+57
-41
Lib/test/test_socketserver.py
Lib/test/test_socketserver.py
+0
-32
No files found.
Doc/library/socketserver.rst
View file @
1d29cc5b
...
...
@@ -113,7 +113,7 @@ the request handler class :meth:`handle` method.
Another approach to handling multiple simultaneous requests in an environment
that supports neither threads nor :func:`~os.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 :
func:`~select.select
` to decide which
partially finished requests and to use :
mod:`selectors
` 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 threads or subprocesses cannot be used). See
...
...
@@ -136,7 +136,7 @@ Server Objects
.. method:: BaseServer.fileno()
Return an integer file descriptor for the socket on which the server is
listening. This function is most commonly passed to :
func:`select.select
`, to
listening. This function is most commonly passed to :
mod:`selectors
`, to
allow monitoring multiple servers in the same process.
...
...
Lib/socketserver.py
View file @
1d29cc5b
...
...
@@ -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
select
ors
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 select
or
Methods that may be overridden:
...
...
@@ -227,14 +229,16 @@ class BaseServer:
"""
self
.
__is_shut_down
.
clear
()
try
:
# 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
:
# 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
:
ready
=
selector
.
select
(
poll_interval
)
if
ready
:
self
.
_handle_request_noblock
()
self
.
service_actions
()
...
...
@@ -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 select
or.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 select
or
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 select
or
.
"""
return
self
.
socket
.
fileno
()
...
...
Lib/test/test_socketserver.py
View file @
1d29cc5b
...
...
@@ -222,38 +222,6 @@ class SocketServerTest(unittest.TestCase):
socketserver
.
DatagramRequestHandler
,
self
.
dgram_examine
)
@
contextlib
.
contextmanager
def
mocked_select_module
(
self
):
"""Mocks the select.select() call to raise EINTR for first call"""
old_select
=
select
.
select
class
MockSelect
:
def
__init__
(
self
):
self
.
called
=
0
def
__call__
(
self
,
*
args
):
self
.
called
+=
1
if
self
.
called
==
1
:
# raise the exception on first call
raise
OSError
(
errno
.
EINTR
,
os
.
strerror
(
errno
.
EINTR
))
else
:
# Return real select value for consecutive calls
return
old_select
(
*
args
)
select
.
select
=
MockSelect
()
try
:
yield
select
.
select
finally
:
select
.
select
=
old_select
def
test_InterruptServerSelectCall
(
self
):
with
self
.
mocked_select_module
()
as
mock_select
:
pid
=
self
.
run_server
(
socketserver
.
TCPServer
,
socketserver
.
StreamRequestHandler
,
self
.
stream_examine
)
# Make sure select was called again:
self
.
assertGreater
(
mock_select
.
called
,
1
)
# Alas, on Linux (at least) recvfrom() doesn't return a meaningful
# client address so this cannot work:
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment