(original) (raw)

changeset: 76167:d941d1fcc6e6 branch: 2.7 parent: 76163:96de17c95a7f user: Antoine Pitrou solipsis@pitrou.net date: Mon Apr 09 00:47:24 2012 +0200 files: Lib/SocketServer.py Lib/test/test_socketserver.py Misc/ACKS Misc/NEWS description: 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. diff -r 96de17c95a7f -r d941d1fcc6e6 Lib/SocketServer.py --- a/Lib/SocketServer.py Sun Apr 08 18:34:36 2012 -0400 +++ b/Lib/SocketServer.py Mon Apr 09 00:47:24 2012 +0200 @@ -133,6 +133,7 @@ import select import sys import os +import errno try: import threading except ImportError: @@ -147,6 +148,15 @@ "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 @@ # 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 @@ 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 -r 96de17c95a7f -r d941d1fcc6e6 Lib/test/test_socketserver.py --- a/Lib/test/test_socketserver.py Sun Apr 08 18:34:36 2012 -0400 +++ b/Lib/test/test_socketserver.py Mon Apr 09 00:47:24 2012 +0200 @@ -8,6 +8,8 @@ import select import signal import socket +import select +import errno import tempfile import unittest import SocketServer @@ -225,6 +227,38 @@ 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 -r 96de17c95a7f -r d941d1fcc6e6 Misc/ACKS --- a/Misc/ACKS Sun Apr 08 18:34:36 2012 -0400 +++ b/Misc/ACKS Mon Apr 09 00:47:24 2012 +0200 @@ -461,6 +461,7 @@ Damon Kohler Marko Kohtala Joseph Koshy +Jerzy Kozera Maksim Kozyarchuk Stefan Krah Bob Kras diff -r 96de17c95a7f -r d941d1fcc6e6 Misc/NEWS --- a/Misc/NEWS Sun Apr 08 18:34:36 2012 -0400 +++ b/Misc/NEWS Mon Apr 09 00:47:24 2012 +0200 @@ -44,6 +44,10 @@ 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. /solipsis@pitrou.net