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 088a874c7f
commit fa1d84107a
4 changed files with 52 additions and 2 deletions

View File

@ -133,6 +133,7 @@ import socket
import select import select
import sys import sys
import os import os
import errno
try: try:
import threading import threading
except ImportError: except ImportError:
@ -147,6 +148,15 @@ if hasattr(socket, "AF_UNIX"):
"ThreadingUnixStreamServer", "ThreadingUnixStreamServer",
"ThreadingUnixDatagramServer"]) "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: class BaseServer:
"""Base class for server classes. """Base class for server classes.
@ -222,7 +232,8 @@ class BaseServer:
# connecting to the socket to wake this up instead of # connecting to the socket to wake this up instead of
# polling. Polling reduces our responsiveness to a # polling. Polling reduces our responsiveness to a
# shutdown request and wastes cpu at all other times. # 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: if self in r:
self._handle_request_noblock() self._handle_request_noblock()
finally: finally:
@ -262,7 +273,7 @@ class BaseServer:
timeout = self.timeout timeout = self.timeout
elif self.timeout is not None: elif self.timeout is not None:
timeout = min(timeout, self.timeout) timeout = min(timeout, self.timeout)
fd_sets = select.select([self], [], [], timeout) fd_sets = _eintr_retry(select.select, [self], [], [], timeout)
if not fd_sets[0]: if not fd_sets[0]:
self.handle_timeout() self.handle_timeout()
return return

View File

@ -8,6 +8,8 @@ import os
import select import select
import signal import signal
import socket import socket
import select
import errno
import tempfile import tempfile
import unittest import unittest
import SocketServer import SocketServer
@ -225,6 +227,38 @@ class SocketServerTest(unittest.TestCase):
SocketServer.DatagramRequestHandler, SocketServer.DatagramRequestHandler,
self.dgram_examine) 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 # Alas, on Linux (at least) recvfrom() doesn't return a meaningful
# client address so this cannot work: # client address so this cannot work:

View File

@ -461,6 +461,7 @@ Greg Kochanski
Damon Kohler Damon Kohler
Marko Kohtala Marko Kohtala
Joseph Koshy Joseph Koshy
Jerzy Kozera
Maksim Kozyarchuk Maksim Kozyarchuk
Stefan Krah Stefan Krah
Bob Kras Bob Kras

View File

@ -44,6 +44,10 @@ Core and Builtins
Library 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 - Issue #14409: IDLE now properly executes commands in the Shell window
when it cannot read the normal config files on startup and when it cannot read the normal config files on startup and
has to use the built-in default key bindings. has to use the built-in default key bindings.