# HG changeset patch # Parent 86b59ac81111ab4a2ebaeb9215f5e45ab38f305e diff -r 86b59ac81111 Doc/library/socketserver.rst --- a/Doc/library/socketserver.rst Mon Feb 09 08:10:32 2015 +0100 +++ b/Doc/library/socketserver.rst Sat Mar 28 05:02:34 2015 +0000 @@ -260,6 +260,9 @@ method raises an exception. The default action is to print the traceback to standard output and continue handling further requests. + .. versionchanged:: 3.5 + Now only called for exceptions derived from the :exc:`Exception` class. + .. method:: BaseServer.handle_timeout() diff -r 86b59ac81111 Lib/socketserver.py --- a/Lib/socketserver.py Mon Feb 09 08:10:32 2015 +0100 +++ b/Lib/socketserver.py Sat Mar 28 05:02:34 2015 +0000 @@ -132,6 +132,7 @@ import selectors import os import errno +import sys try: import threading except ImportError: @@ -316,9 +317,12 @@ if self.verify_request(request, client_address): try: self.process_request(request, client_address) - except: + except Exception: self.handle_error(request, client_address) self.shutdown_request(request) + except: + self.shutdown_request(request) + raise def handle_timeout(self): """Called if no new request arrives within self.timeout. @@ -370,12 +374,12 @@ The default is to print a traceback and continue. """ - print('-'*40) - print('Exception happened during processing of request from', end=' ') - print(client_address) + print('-'*40, file=sys.stderr) + print('Exception happened during processing of request from', + client_address, file=sys.stderr) import traceback - traceback.print_exc() # XXX But this goes to stderr! - print('-'*40) + traceback.print_exc() + print('-'*40, file=sys.stderr) class TCPServer(BaseServer): @@ -599,16 +603,17 @@ else: # Child process. # This must never return, hence os._exit()! + status = 1 try: self.finish_request(request, client_address) - self.shutdown_request(request) - os._exit(0) - except: + status = 0 + except Exception: + self.handle_error(request, client_address) + finally: try: - self.handle_error(request, client_address) self.shutdown_request(request) finally: - os._exit(1) + os._exit(status) class ThreadingMixIn: @@ -626,9 +631,9 @@ """ try: self.finish_request(request, client_address) - self.shutdown_request(request) - except: + except Exception: self.handle_error(request, client_address) + finally: self.shutdown_request(request) def process_request(self, request, client_address): diff -r 86b59ac81111 Lib/test/test_socketserver.py --- a/Lib/test/test_socketserver.py Mon Feb 09 08:10:32 2015 +0100 +++ b/Lib/test/test_socketserver.py Sat Mar 28 05:02:34 2015 +0000 @@ -58,6 +58,7 @@ @contextlib.contextmanager def simple_subprocess(testcase): + """Tests that a custom child process is not waited on (Issue 1540386)""" pid = os.fork() if pid == 0: # Don't raise an exception; it would be caught by the test harness. @@ -280,6 +281,93 @@ socketserver.StreamRequestHandler) +class ErrorHandlerTest(unittest.TestCase): + """Test that the servers pass normal exceptions from the handler to + handle_error(), and that exiting exceptions like SystemExit and + KeyboardInterrupt are not.""" + + def tearDown(self): + test.support.unlink(test.support.TESTFN) + + def test_sync_handled(self): + BaseErrorTestServer(ValueError) + self.check_result(handled=True) + + def test_sync_not_handled(self): + with self.assertRaises(SystemExit): + BaseErrorTestServer(SystemExit) + self.check_result(handled=False) + + @unittest.skipUnless(threading, 'Threading required for this test.') + def test_threading_handled(self): + ThreadingErrorTestServer(ValueError) + self.check_result(handled=True) + + @unittest.skipUnless(threading, 'Threading required for this test.') + def test_threading_not_handled(self): + ThreadingErrorTestServer(SystemExit) + self.check_result(handled=False) + + @requires_forking + def test_forking_handled(self): + ForkingErrorTestServer(ValueError) + self.check_result(handled=True) + + @requires_forking + def test_forking_not_handled(self): + ForkingErrorTestServer(SystemExit) + self.check_result(handled=False) + + def check_result(self, handled): + with open(test.support.TESTFN) as log: + expected = 'Handler called\n' + 'Error handled\n' * handled + self.assertEqual(log.read(), expected) + +class BaseErrorTestServer(socketserver.TCPServer): + def __init__(self, exception): + self.exception = exception + super().__init__((HOST, 0), BadHandler) + with socket.create_connection(self.server_address): + pass + try: + self.handle_request() + finally: + self.server_close() + self.wait_done() + + def handle_error(self, request, client_address): + with open(test.support.TESTFN, 'a') as log: + log.write('Error handled\n') + + def wait_done(self): + pass + +class BadHandler(socketserver.BaseRequestHandler): + def handle(self): + with open(test.support.TESTFN, 'a') as log: + log.write('Handler called\n') + raise self.server.exception('Test error') + +class ThreadingErrorTestServer(socketserver.ThreadingMixIn, + BaseErrorTestServer): + def __init__(self, *pos, **kw): + self.done = threading.Event() + return super().__init__(*pos, **kw) + + def shutdown_request(self, *pos, **kw): + super().shutdown_request(*pos, **kw) + self.done.set() + + def wait_done(self): + self.done.wait() + +class ForkingErrorTestServer(socketserver.ForkingMixIn, BaseErrorTestServer): + def wait_done(self): + [child] = self.active_children + os.waitpid(child, 0) + self.active_children.clear() + + class MiscTestCase(unittest.TestCase): def test_all(self):