Index: Lib/socketserver.py =================================================================== --- Lib/socketserver.py (revision 79903) +++ Lib/socketserver.py (working copy) @@ -133,6 +133,7 @@ import select import sys import os +import errno try: import threading except ImportError: @@ -147,6 +148,17 @@ "ThreadingUnixStreamServer", "ThreadingUnixDatagramServer"]) + +def _eintr_retry(func, *args): + """restart a system call interrupted by EINTR""" + while True: + try: + return func(*args) + except (OSError, IOError, select.error) as e: + if e.args[0] != errno.EINTR: + raise + + class BaseServer: """Base class for server classes. @@ -222,7 +234,7 @@ # 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 r: self._handle_request_noblock() self.__is_shut_down.set() @@ -260,7 +272,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 Index: Lib/test/test_socketserver.py =================================================================== --- Lib/test/test_socketserver.py (revision 79903) +++ Lib/test/test_socketserver.py (working copy) @@ -12,6 +12,7 @@ import threading import unittest import socketserver +import time import test.support from test.support import reap_children, reap_threads, verbose @@ -36,14 +37,13 @@ else: raise RuntimeError("timed out on %r" % (sock,)) -if HAVE_UNIX_SOCKETS: - class ForkingUnixStreamServer(socketserver.ForkingMixIn, - socketserver.UnixStreamServer): - pass +class ForkingUnixStreamServer(socketserver.ForkingMixIn, + socketserver.UnixStreamServer): + pass - class ForkingUnixDatagramServer(socketserver.ForkingMixIn, - socketserver.UnixDatagramServer): - pass +class ForkingUnixDatagramServer(socketserver.ForkingMixIn, + socketserver.UnixDatagramServer): + pass @contextlib.contextmanager @@ -58,7 +58,7 @@ testcase.assertEquals(72 << 8, status) -class SocketServerTest(unittest.TestCase): +class BaseSocketServerTestCase(unittest.TestCase): """Test all socket servers.""" def setUp(self): @@ -169,6 +169,7 @@ self.assertEquals(buf, TEST_STR) s.close() +class SimpleSocketServerTestCase(BaseSocketServerTestCase): def test_TCPServer(self): self.run_server(socketserver.TCPServer, socketserver.StreamRequestHandler, @@ -179,76 +180,134 @@ socketserver.StreamRequestHandler, self.stream_examine) - if HAVE_FORKING: - def test_ForkingTCPServer(self): - with simple_subprocess(self): - self.run_server(socketserver.ForkingTCPServer, - socketserver.StreamRequestHandler, - self.stream_examine) + def test_UDPServer(self): + self.run_server(socketserver.UDPServer, + socketserver.DatagramRequestHandler, + self.dgram_examine) - if HAVE_UNIX_SOCKETS: - def test_UnixStreamServer(self): - self.run_server(socketserver.UnixStreamServer, - socketserver.StreamRequestHandler, - self.stream_examine) + def test_ThreadingUDPServer(self): + self.run_server(socketserver.ThreadingUDPServer, + socketserver.DatagramRequestHandler, + self.dgram_examine) - def test_ThreadingUnixStreamServer(self): - self.run_server(socketserver.ThreadingUnixStreamServer, +class ForkingSocketServerTestCase(BaseSocketServerTestCase): + def test_ForkingTCPServer(self): + with simple_subprocess(self): + self.run_server(socketserver.ForkingTCPServer, socketserver.StreamRequestHandler, self.stream_examine) - if HAVE_FORKING: - def test_ForkingUnixStreamServer(self): - with simple_subprocess(self): - self.run_server(ForkingUnixStreamServer, - socketserver.StreamRequestHandler, - self.stream_examine) + def test_ForkingUDPServer(self): + with simple_subprocess(self): + self.run_server(socketserver.ForkingUDPServer, + socketserver.DatagramRequestHandler, + self.dgram_examine) - def test_UDPServer(self): - self.run_server(socketserver.UDPServer, +class UnixSocketsSocketServerTestCase(BaseSocketServerTestCase): + def test_UnixStreamServer(self): + self.run_server(socketserver.UnixStreamServer, + socketserver.StreamRequestHandler, + self.stream_examine) + + def test_ThreadingUnixStreamServer(self): + self.run_server(socketserver.ThreadingUnixStreamServer, + socketserver.StreamRequestHandler, + self.stream_examine) + + # XXX Alas, on Linux (at least) recvfrom() doesn't return a meaningful + # client address so the following two tests don't work; fix or erase? + def DISABLED_test_UnixDatagramServer(self): + self.run_server(socketserver.UnixDatagramServer, socketserver.DatagramRequestHandler, self.dgram_examine) - def test_ThreadingUDPServer(self): - self.run_server(socketserver.ThreadingUDPServer, + def DISABLED_test_ThreadingUnixDatagramServer(self): + self.run_server(socketserver.ThreadingUnixDatagramServer, socketserver.DatagramRequestHandler, self.dgram_examine) - if HAVE_FORKING: - def test_ForkingUDPServer(self): - with simple_subprocess(self): - self.run_server(socketserver.ForkingUDPServer, - socketserver.DatagramRequestHandler, - self.dgram_examine) +class ForkingUnixSocketsSocketServerTestCase(BaseSocketServerTestCase): + def test_ForkingUnixStreamServer(self): + with simple_subprocess(self): + self.run_server(ForkingUnixStreamServer, + socketserver.StreamRequestHandler, + self.stream_examine) - # Alas, on Linux (at least) recvfrom() doesn't return a meaningful - # client address so this cannot work: + # XXX this test also does not work; fix it or erase it? + def DISABLED_test_ForkingUnixDatagramServer(self): + self.run_server(socketserver.ForkingUnixDatagramServer, + socketserver.DatagramRequestHandler, + self.dgram_examine) - # if HAVE_UNIX_SOCKETS: - # def test_UnixDatagramServer(self): - # self.run_server(socketserver.UnixDatagramServer, - # socketserver.DatagramRequestHandler, - # self.dgram_examine) - # - # def test_ThreadingUnixDatagramServer(self): - # self.run_server(socketserver.ThreadingUnixDatagramServer, - # socketserver.DatagramRequestHandler, - # self.dgram_examine) - # - # if HAVE_FORKING: - # def test_ForkingUnixDatagramServer(self): - # self.run_server(socketserver.ForkingUnixDatagramServer, - # socketserver.DatagramRequestHandler, - # self.dgram_examine) +class InterruptingSocketServerTestCase(BaseSocketServerTestCase): + def fork_server(self, svrcls, hdlrbase): + pid = os.fork() + if pid == 0: + # child: must never resume execution beyond this block + server = self.make_server(self.pickaddr(svrcls.address_family), + svrcls, hdlrbase) + signal.signal(signal.SIGUSR1, lambda sig, frame: None) + try: + if verbose: print("server running, pid %d" % (os.getpid(),)) + server.serve_forever() + except Exception as error: + if verbose: print("server raised unexpected %s" % (error,)) + finally: + os._exit(1) + # child: no matter what, child dies here + return pid + def kill_and_validate_server(self, pid): + if verbose: print("killing server at %d" % (pid,)) + os.kill(pid, signal.SIGTERM) + pid2, status = os.waitpid(pid, 0) + self.assertEquals(pid2, pid) + self.assertEquals(signal.SIGTERM, status) + def test_InterruptServerSelectCall(self): + pid = self.fork_server(socketserver.TCPServer, + socketserver.StreamRequestHandler) + + # XXX give the server time to reach select(); not very elegant, + # but anything else I can think of will be overly complicated + # for a unittest + time.sleep(0.2) + os.kill(pid, signal.SIGUSR1) + # XXX give the server time to handle this SIGUSR1 (and possible die + # from an exception raised because of it) before we send the + # SIGTERM + time.sleep(0.2) + + self.kill_and_validate_server(pid) + if verbose: print("done") + + def stream_examine(self, proto, addr): + s = socket.socket(proto, socket.SOCK_STREAM) + s.connect(addr) + s.sendall(TEST_STR) + buf = data = receive(s, 100) + while data and b'\n' not in buf: + data = receive(s, 100) + buf += data + self.assertEquals(buf, TEST_STR) + s.close() + def test_main(): if imp.lock_held(): # If the import lock is held, the threads will hang raise unittest.SkipTest("can't run when import lock is held") - test.support.run_unittest(SocketServerTest) + unit_tests = [SimpleSocketServerTestCase] + if HAVE_FORKING: + unit_tests.append(ForkingSocketServerTestCase) + unit_tests.append(InterruptingSocketServerTestCase) + if HAVE_UNIX_SOCKETS: + unit_tests.append(UnixSocketsSocketServerTestCase) + if HAVE_UNIX_SOCKETS and HAVE_FORKING: + unit_tests.append(ForkingUnixSocketsSocketServerTestCase) + test.support.run_unittest(*unit_tests) + if __name__ == "__main__": test_main() signal_alarm(3) # Shutdown shouldn't take more than 3 seconds.