cpython: d500d1a9615f (original) (raw)
Mercurial > cpython
changeset 100285:d500d1a9615f
Issue #23430: Stop socketserver from catching SystemExit etc from handlers Also make handle_error() consistently output to stderr, and fix the documentation. [#23430]
Martin Panter vadmium+py@gmail.com | |
---|---|
date | Sun, 21 Feb 2016 08:49:56 +0000 |
parents | 2d746fb08d0d |
children | f555ef42ad62 |
files | Doc/library/socketserver.rst Doc/whatsnew/3.6.rst Lib/socketserver.py Lib/test/test_socketserver.py Misc/NEWS |
diffstat | 5 files changed, 131 insertions(+), 15 deletions(-)[+] [-] Doc/library/socketserver.rst 6 Doc/whatsnew/3.6.rst 11 Lib/socketserver.py 31 Lib/test/test_socketserver.py 92 Misc/NEWS 6 |
line wrap: on
line diff
--- a/Doc/library/socketserver.rst
+++ b/Doc/library/socketserver.rst
@@ -304,7 +304,11 @@ Server Objects
This function is called if the :meth:~BaseRequestHandler.handle
method of a :attr:RequestHandlerClass
instance raises
an exception. The default action is to print the traceback to
standard output and continue handling further requests.[](#l1.7)
standard error and continue handling further requests.[](#l1.8)
.. versionchanged:: 3.6[](#l1.10)
Now only called for exceptions derived from the :exc:`Exception`[](#l1.11)
class.[](#l1.12)
--- a/Doc/whatsnew/3.6.rst +++ b/Doc/whatsnew/3.6.rst @@ -334,7 +334,16 @@ Changes in the Python API
- When a relative import is performed and no parent package is known, then
:exc:
ImportError
will be raised. Previously, :exc:SystemError
could be
- raised. (Contributed by Brett Cannon in :issue:
18018
.) + +* Servers based on the :mod:socketserver
module, including those - defined in :mod:
http.server
, :mod:xmlrpc.server
and - :mod:
wsgiref.simple_server
, now only catch exceptions derived - from :exc:
Exception
. Therefore if a request handler raises - an exception like :exc:
SystemExit
or :exc:KeyboardInterrupt
, - :meth:
~socketserver.BaseServer.handle_error
is no longer called, and - the exception will stop a single-threaded server. (Contributed by
- Martin Panter in :issue:
23430
.) Changes in the C API
--- a/Lib/socketserver.py +++ b/Lib/socketserver.py @@ -132,6 +132,7 @@ import socket import selectors import os import errno +import sys try: import threading except ImportError: @@ -316,9 +317,12 @@ class BaseServer: if self.verify_request(request, client_address): try: self.process_request(request, client_address)
except:[](#l3.15)
except Exception:[](#l3.16) self.handle_error(request, client_address)[](#l3.17) self.shutdown_request(request)[](#l3.18)
except:[](#l3.19)
self.shutdown_request(request)[](#l3.20)
raise[](#l3.21) else:[](#l3.22) self.shutdown_request(request)[](#l3.23)
@@ -372,12 +376,12 @@ class BaseServer: The default is to print a traceback and continue. """
print('-'*40)[](#l3.29)
print('Exception happened during processing of request from', end=' ')[](#l3.30)
print(client_address)[](#l3.31)
print('-'*40, file=sys.stderr)[](#l3.32)
print('Exception happened during processing of request from',[](#l3.33)
client_address, file=sys.stderr)[](#l3.34) import traceback[](#l3.35)
traceback.print_exc() # XXX But this goes to stderr
print('-'*40)[](#l3.37)
traceback.print_exc()[](#l3.38)
print('-'*40, file=sys.stderr)[](#l3.39)
class TCPServer(BaseServer):
@@ -601,16 +605,17 @@ class ForkingMixIn:
else:
# Child process.
# This must never return, hence os._exit()
status = 1[](#l3.47) try:[](#l3.48) self.finish_request(request, client_address)[](#l3.49)
self.shutdown_request(request)[](#l3.50)
os._exit(0)[](#l3.51)
except:[](#l3.52)
status = 0[](#l3.53)
except Exception:[](#l3.54)
self.handle_error(request, client_address)[](#l3.55)
finally:[](#l3.56) try:[](#l3.57)
self.handle_error(request, client_address)[](#l3.58) self.shutdown_request(request)[](#l3.59) finally:[](#l3.60)
os._exit(1)[](#l3.61)
os._exit(status)[](#l3.62)
class ThreadingMixIn: @@ -628,9 +633,9 @@ class ThreadingMixIn: """ try: self.finish_request(request, client_address)
self.shutdown_request(request)[](#l3.70)
except:[](#l3.71)
except Exception:[](#l3.72) self.handle_error(request, client_address)[](#l3.73)
finally:[](#l3.74) self.shutdown_request(request)[](#l3.75)
def process_request(self, request, client_address):
--- a/Lib/test/test_socketserver.py +++ b/Lib/test/test_socketserver.py @@ -58,6 +58,7 @@ if HAVE_UNIX_SOCKETS: @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.
@@ -281,6 +282,97 @@ class SocketServerTest(unittest.TestCase 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 passed."""
- def test_sync_handled(self):
BaseErrorTestServer(ValueError)[](#l4.24)
self.check_result(handled=True)[](#l4.25)
- def test_sync_not_handled(self):
with self.assertRaises(SystemExit):[](#l4.28)
BaseErrorTestServer(SystemExit)[](#l4.29)
self.check_result(handled=False)[](#l4.30)
- @unittest.skipUnless(threading, 'Threading required for this test.')
- def test_threading_handled(self):
ThreadingErrorTestServer(ValueError)[](#l4.34)
self.check_result(handled=True)[](#l4.35)
- @unittest.skipUnless(threading, 'Threading required for this test.')
- def test_threading_not_handled(self):
ThreadingErrorTestServer(SystemExit)[](#l4.39)
self.check_result(handled=False)[](#l4.40)
- @requires_forking
- def test_forking_handled(self):
ForkingErrorTestServer(ValueError)[](#l4.44)
self.check_result(handled=True)[](#l4.45)
- @requires_forking
- def test_forking_not_handled(self):
ForkingErrorTestServer(SystemExit)[](#l4.49)
self.check_result(handled=False)[](#l4.50)
- def check_result(self, handled):
with open(test.support.TESTFN) as log:[](#l4.53)
expected = 'Handler called\n' + 'Error handled\n' * handled[](#l4.54)
self.assertEqual(log.read(), expected)[](#l4.55)
+ + +class BaseErrorTestServer(socketserver.TCPServer):
- def init(self, exception):
self.exception = exception[](#l4.60)
super().__init__((HOST, 0), BadHandler)[](#l4.61)
with socket.create_connection(self.server_address):[](#l4.62)
pass[](#l4.63)
try:[](#l4.64)
self.handle_request()[](#l4.65)
finally:[](#l4.66)
self.server_close()[](#l4.67)
self.wait_done()[](#l4.68)
- def handle_error(self, request, client_address):
with open(test.support.TESTFN, 'a') as log:[](#l4.71)
log.write('Error handled\n')[](#l4.72)
+ + +class BadHandler(socketserver.BaseRequestHandler):
- def handle(self):
with open(test.support.TESTFN, 'a') as log:[](#l4.80)
log.write('Handler called\n')[](#l4.81)
raise self.server.exception('Test error')[](#l4.82)
+ + +class ThreadingErrorTestServer(socketserver.ThreadingMixIn,
BaseErrorTestServer):[](#l4.86)
- def init(self, *pos, **kw):
self.done = threading.Event()[](#l4.88)
super().__init__(*pos, **kw)[](#l4.89)
- def shutdown_request(self, *pos, **kw):
super().shutdown_request(*pos, **kw)[](#l4.92)
self.done.set()[](#l4.93)
+ + +class ForkingErrorTestServer(socketserver.ForkingMixIn, BaseErrorTestServer):
- def wait_done(self):
[child] = self.active_children[](#l4.101)
os.waitpid(child, 0)[](#l4.102)
self.active_children.clear()[](#l4.103)
+ + class MiscTestCase(unittest.TestCase): def test_all(self):
--- a/Misc/NEWS +++ b/Misc/NEWS @@ -196,6 +196,12 @@ Issue #26186: Remove an invalid type che the connected socket) when verify_request() returns false. Patch by Aviv Palivoda. +- Issue #23430: Change the socketserver module to only catch exceptions