Issue #23430: Stop socketserver from catching SystemExit etc from handlers
Also make handle_error() consistently output to stderr, and fix the documentation.
This commit is contained in:
parent
86a8be00ed
commit
d9108d1253
@ -304,7 +304,11 @@ Server Objects
|
|||||||
This function is called if the :meth:`~BaseRequestHandler.handle`
|
This function is called if the :meth:`~BaseRequestHandler.handle`
|
||||||
method of a :attr:`RequestHandlerClass` instance raises
|
method of a :attr:`RequestHandlerClass` instance raises
|
||||||
an exception. The default action is to print the traceback to
|
an exception. The default action is to print the traceback to
|
||||||
standard output and continue handling further requests.
|
standard error and continue handling further requests.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.6
|
||||||
|
Now only called for exceptions derived from the :exc:`Exception`
|
||||||
|
class.
|
||||||
|
|
||||||
|
|
||||||
.. method:: handle_timeout()
|
.. method:: handle_timeout()
|
||||||
|
@ -334,7 +334,16 @@ Changes in the Python API
|
|||||||
|
|
||||||
* When a relative import is performed and no parent package is known, then
|
* When a relative import is performed and no parent package is known, then
|
||||||
:exc:`ImportError` will be raised. Previously, :exc:`SystemError` could be
|
:exc:`ImportError` will be raised. Previously, :exc:`SystemError` could be
|
||||||
raised. (Contribute by Brett Cannon in :issue:`18018`.)
|
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
|
Changes in the C API
|
||||||
|
@ -132,6 +132,7 @@ import socket
|
|||||||
import selectors
|
import selectors
|
||||||
import os
|
import os
|
||||||
import errno
|
import errno
|
||||||
|
import sys
|
||||||
try:
|
try:
|
||||||
import threading
|
import threading
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@ -316,9 +317,12 @@ class BaseServer:
|
|||||||
if self.verify_request(request, client_address):
|
if self.verify_request(request, client_address):
|
||||||
try:
|
try:
|
||||||
self.process_request(request, client_address)
|
self.process_request(request, client_address)
|
||||||
except:
|
except Exception:
|
||||||
self.handle_error(request, client_address)
|
self.handle_error(request, client_address)
|
||||||
self.shutdown_request(request)
|
self.shutdown_request(request)
|
||||||
|
except:
|
||||||
|
self.shutdown_request(request)
|
||||||
|
raise
|
||||||
else:
|
else:
|
||||||
self.shutdown_request(request)
|
self.shutdown_request(request)
|
||||||
|
|
||||||
@ -372,12 +376,12 @@ class BaseServer:
|
|||||||
The default is to print a traceback and continue.
|
The default is to print a traceback and continue.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
print('-'*40)
|
print('-'*40, file=sys.stderr)
|
||||||
print('Exception happened during processing of request from', end=' ')
|
print('Exception happened during processing of request from',
|
||||||
print(client_address)
|
client_address, file=sys.stderr)
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc() # XXX But this goes to stderr!
|
traceback.print_exc()
|
||||||
print('-'*40)
|
print('-'*40, file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
class TCPServer(BaseServer):
|
class TCPServer(BaseServer):
|
||||||
@ -601,16 +605,17 @@ class ForkingMixIn:
|
|||||||
else:
|
else:
|
||||||
# Child process.
|
# Child process.
|
||||||
# This must never return, hence os._exit()!
|
# This must never return, hence os._exit()!
|
||||||
|
status = 1
|
||||||
try:
|
try:
|
||||||
self.finish_request(request, client_address)
|
self.finish_request(request, client_address)
|
||||||
self.shutdown_request(request)
|
status = 0
|
||||||
os._exit(0)
|
except Exception:
|
||||||
except:
|
self.handle_error(request, client_address)
|
||||||
|
finally:
|
||||||
try:
|
try:
|
||||||
self.handle_error(request, client_address)
|
|
||||||
self.shutdown_request(request)
|
self.shutdown_request(request)
|
||||||
finally:
|
finally:
|
||||||
os._exit(1)
|
os._exit(status)
|
||||||
|
|
||||||
|
|
||||||
class ThreadingMixIn:
|
class ThreadingMixIn:
|
||||||
@ -628,9 +633,9 @@ class ThreadingMixIn:
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.finish_request(request, client_address)
|
self.finish_request(request, client_address)
|
||||||
self.shutdown_request(request)
|
except Exception:
|
||||||
except:
|
|
||||||
self.handle_error(request, client_address)
|
self.handle_error(request, client_address)
|
||||||
|
finally:
|
||||||
self.shutdown_request(request)
|
self.shutdown_request(request)
|
||||||
|
|
||||||
def process_request(self, request, client_address):
|
def process_request(self, request, client_address):
|
||||||
|
@ -58,6 +58,7 @@ if HAVE_UNIX_SOCKETS:
|
|||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def simple_subprocess(testcase):
|
def simple_subprocess(testcase):
|
||||||
|
"""Tests that a custom child process is not waited on (Issue 1540386)"""
|
||||||
pid = os.fork()
|
pid = os.fork()
|
||||||
if pid == 0:
|
if pid == 0:
|
||||||
# Don't raise an exception; it would be caught by the test harness.
|
# Don't raise an exception; it would be caught by the test harness.
|
||||||
@ -281,6 +282,97 @@ class SocketServerTest(unittest.TestCase):
|
|||||||
socketserver.StreamRequestHandler)
|
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 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()
|
||||||
|
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):
|
class MiscTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def test_all(self):
|
def test_all(self):
|
||||||
|
@ -196,6 +196,12 @@ Issue #26186: Remove an invalid type check in importlib.util.LazyLoader.
|
|||||||
the connected socket) when verify_request() returns false. Patch by Aviv
|
the connected socket) when verify_request() returns false. Patch by Aviv
|
||||||
Palivoda.
|
Palivoda.
|
||||||
|
|
||||||
|
- Issue #23430: Change the socketserver module to only catch exceptions
|
||||||
|
raised from a request handler that are derived from Exception (instead of
|
||||||
|
BaseException). Therefore SystemExit and KeyboardInterrupt no longer
|
||||||
|
trigger the handle_error() method, and will now to stop a single-threaded
|
||||||
|
server.
|
||||||
|
|
||||||
- Issue #25939: On Windows open the cert store readonly in ssl.enum_certificates.
|
- Issue #25939: On Windows open the cert store readonly in ssl.enum_certificates.
|
||||||
|
|
||||||
- Issue #25995: os.walk() no longer uses FDs proportional to the tree depth.
|
- Issue #25995: os.walk() no longer uses FDs proportional to the tree depth.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user