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:
Antoine Pitrou 2012-04-09 00:47:24 +02:00
parent 317c8d257e
commit b0a9c66a49
4 changed files with 52 additions and 2 deletions

View File

@ -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

View File

@ -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:

View File

@ -510,6 +510,7 @@ Damon Kohler
Marko Kohtala
Vlad Korolev
Joseph Koshy
Jerzy Kozera
Maksim Kozyarchuk
Stefan Krah
Bob Kras

View File

@ -39,6 +39,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 #14496: Fix wrong name in idlelib/tabbedpages.py.
Patch by Popa Claudiu.