summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/socketserver.py15
-rw-r--r--Lib/test/test_socketserver.py34
-rw-r--r--Misc/ACKS1
-rw-r--r--Misc/NEWS4
4 files changed, 52 insertions, 2 deletions
diff --git a/Lib/socketserver.py b/Lib/socketserver.py
index a487e63..261e28e 100644
--- a/Lib/socketserver.py
+++ b/Lib/socketserver.py
@@ -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.
@@ -223,7 +233,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()
@@ -273,7 +284,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
diff --git a/Lib/test/test_socketserver.py b/Lib/test/test_socketserver.py
index b3660c1..0e76c33 100644
--- a/Lib/test/test_socketserver.py
+++ b/Lib/test/test_socketserver.py
@@ -8,6 +8,8 @@ import os
import select
import signal
import socket
+import select
+import errno
import tempfile
import unittest
import socketserver
@@ -226,6 +228,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:
diff --git a/Misc/ACKS b/Misc/ACKS
index 800e884..cab01d4 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -559,6 +559,7 @@ Jacek Konieczny
Arkady Koplyarov
Vlad Korolev
Joseph Koshy
+Jerzy Kozera
Maksim Kozyarchuk
Stefan Krah
Bob Kras
diff --git a/Misc/NEWS b/Misc/NEWS
index af1b5d0..6256e02 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -19,6 +19,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 #14522: Avoid duplicating socket handles in multiprocessing.connection.
Patch by sbt.