Issue #7978: socketserver now restarts the select() call when EINTR is returned.
This avoids crashing the server loop when a signal is received. Patch by Jerzy Kozera.
This commit is contained in:
parent
088a874c7f
commit
fa1d84107a
|
@ -133,6 +133,7 @@ import socket
|
|||
import select
|
||||
import sys
|
||||
import os
|
||||
import errno
|
||||
try:
|
||||
import threading
|
||||
except ImportError:
|
||||
|
@ -147,6 +148,15 @@ 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
|
||||
|
||||
class BaseServer:
|
||||
|
||||
"""Base class for server classes.
|
||||
|
@ -222,7 +232,8 @@ class BaseServer:
|
|||
# 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 = select.select([self], [], [], poll_interval)
|
||||
r, w, e = _eintr_retry(select.select, [self], [], [],
|
||||
poll_interval)
|
||||
if self in r:
|
||||
self._handle_request_noblock()
|
||||
finally:
|
||||
|
@ -262,7 +273,7 @@ class BaseServer:
|
|||
timeout = self.timeout
|
||||
elif self.timeout is not None:
|
||||
timeout = min(timeout, self.timeout)
|
||||
fd_sets = select.select([self], [], [], timeout)
|
||||
fd_sets = _eintr_retry(select.select, [self], [], [], timeout)
|
||||
if not fd_sets[0]:
|
||||
self.handle_timeout()
|
||||
return
|
||||
|
|
|
@ -8,6 +8,8 @@ import os
|
|||
import select
|
||||
import signal
|
||||
import socket
|
||||
import select
|
||||
import errno
|
||||
import tempfile
|
||||
import unittest
|
||||
import SocketServer
|
||||
|
@ -225,6 +227,38 @@ 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:
|
||||
|
||||
|
|
|
@ -461,6 +461,7 @@ Greg Kochanski
|
|||
Damon Kohler
|
||||
Marko Kohtala
|
||||
Joseph Koshy
|
||||
Jerzy Kozera
|
||||
Maksim Kozyarchuk
|
||||
Stefan Krah
|
||||
Bob Kras
|
||||
|
|
|
@ -44,6 +44,10 @@ Core and Builtins
|
|||
Library
|
||||
-------
|
||||
|
||||
- Issue #7978: SocketServer now restarts the select() call when EINTR is
|
||||
returned. This avoids crashing the server loop when a signal is received.
|
||||
Patch by Jerzy Kozera.
|
||||
|
||||
- Issue #14409: IDLE now properly executes commands in the Shell window
|
||||
when it cannot read the normal config files on startup and
|
||||
has to use the built-in default key bindings.
|
||||
|
|
Loading…
Reference in New Issue