[Python-checkins] cpython: Issue #23566: enable(), register(), dump_traceback() and dump_traceback_later()

victor.stinner python-checkins at python.org
Thu Mar 12 15:33:19 CET 2015


https://hg.python.org/cpython/rev/e78de5b1e3ef
changeset: 94956:e78de5b1e3ef
user: Victor Stinner <victor.stinner at gmail.com>
date: Thu Mar 12 15:32:03 2015 +0100
summary:
 Issue #23566: enable(), register(), dump_traceback() and dump_traceback_later()
functions of faulthandler now accept file descriptors. Patch by Wei Wu.
files:
 Doc/library/faulthandler.rst | 24 +++
 Doc/whatsnew/3.5.rst | 8 +
 Lib/test/test_faulthandler.py | 158 ++++++++++++++-------
 Misc/NEWS | 4 +
 Modules/faulthandler.c | 56 ++++--
 5 files changed, 172 insertions(+), 78 deletions(-)
diff --git a/Doc/library/faulthandler.rst b/Doc/library/faulthandler.rst
--- a/Doc/library/faulthandler.rst
+++ b/Doc/library/faulthandler.rst
@@ -47,6 +47,9 @@
 Dump the tracebacks of all threads into *file*. If *all_threads* is
 ``False``, dump only the current thread.
 
+ .. versionchanged:: 3.5
+ Added support for passing file descriptor to this function.
+
 
 Fault handler state
 -------------------
@@ -59,6 +62,12 @@
 produce tracebacks for every running thread. Otherwise, dump only the current
 thread.
 
+ The *file* must be kept open until the fault handler is disabled: see
+ :ref:`issue with file descriptors <faulthandler-fd>`.
+
+ .. versionchanged:: 3.5
+ Added support for passing file descriptor to this function.
+
 .. function:: disable()
 
 Disable the fault handler: uninstall the signal handlers installed by
@@ -82,9 +91,16 @@
 call replaces previous parameters and resets the timeout. The timer has a
 sub-second resolution.
 
+ The *file* must be kept open until the traceback is dumped or
+ :func:`cancel_dump_traceback_later` is called: see :ref:`issue with file
+ descriptors <faulthandler-fd>`.
+
 This function is implemented using a watchdog thread and therefore is not
 available if Python is compiled with threads disabled.
 
+ .. versionchanged:: 3.5
+ Added support for passing file descriptor to this function.
+
 .. function:: cancel_dump_traceback_later()
 
 Cancel the last call to :func:`dump_traceback_later`.
@@ -99,8 +115,14 @@
 the traceback of all threads, or of the current thread if *all_threads* is
 ``False``, into *file*. Call the previous handler if chain is ``True``.
 
+ The *file* must be kept open until the signal is unregistered by
+ :func:`unregister`: see :ref:`issue with file descriptors <faulthandler-fd>`.
+
 Not available on Windows.
 
+ .. versionchanged:: 3.5
+ Added support for passing file descriptor to this function.
+
 .. function:: unregister(signum)
 
 Unregister a user signal: uninstall the handler of the *signum* signal
@@ -110,6 +132,8 @@
 Not available on Windows.
 
 
+.. _faulthandler-fd:
+
 Issue with file descriptors
 ---------------------------
 
diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst
--- a/Doc/whatsnew/3.5.rst
+++ b/Doc/whatsnew/3.5.rst
@@ -417,6 +417,14 @@
 * :class:`xmlrpc.client.ServerProxy` is now a :term:`context manager`.
 (Contributed by Claudiu Popa in :issue:`20627`.)
 
+faulthandler
+------------
+
+* :func:`~faulthandler.enable`, :func:`~faulthandler.register`,
+ :func:`~faulthandler.dump_traceback` and
+ :func:`~faulthandler.dump_traceback_later` functions now accept file
+ descriptors. (Contributed by Wei Wu in :issue:`23566`.)
+
 
 Optimizations
 =============
diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py
--- a/Lib/test/test_faulthandler.py
+++ b/Lib/test/test_faulthandler.py
@@ -42,7 +42,7 @@
 support.unlink(filename)
 
 class FaultHandlerTests(unittest.TestCase):
- def get_output(self, code, filename=None):
+ def get_output(self, code, filename=None, fd=None):
 """
 Run the specified code in Python (in a new child process) and read the
 output from the standard error or from a file (if filename is set).
@@ -53,8 +53,11 @@
 thread XXX".
 """
 code = dedent(code).strip()
+ pass_fds = []
+ if fd is not None:
+ pass_fds.append(fd)
 with support.SuppressCrashReport():
- process = script_helper.spawn_python('-c', code)
+ process = script_helper.spawn_python('-c', code, pass_fds=pass_fds)
 stdout, stderr = process.communicate()
 exitcode = process.wait()
 output = support.strip_python_stderr(stdout)
@@ -64,13 +67,20 @@
 with open(filename, "rb") as fp:
 output = fp.read()
 output = output.decode('ascii', 'backslashreplace')
+ elif fd is not None:
+ self.assertEqual(output, '')
+ os.lseek(fd, os.SEEK_SET, 0)
+ with open(fd, "rb", closefd=False) as fp:
+ output = fp.read()
+ output = output.decode('ascii', 'backslashreplace')
 output = re.sub('Current thread 0x[0-9a-f]+',
 'Current thread XXX',
 output)
 return output.splitlines(), exitcode
 
 def check_fatal_error(self, code, line_number, name_regex,
- filename=None, all_threads=True, other_regex=None):
+ filename=None, all_threads=True, other_regex=None,
+ fd=None):
 """
 Check that the fault handler for fatal errors is enabled and check the
 traceback from the child process output.
@@ -93,7 +103,7 @@
 header=re.escape(header))).strip()
 if other_regex:
 regex += '|' + other_regex
- output, exitcode = self.get_output(code, filename)
+ output, exitcode = self.get_output(code, filename=filename, fd=fd)
 output = '\n'.join(output)
 self.assertRegex(output, regex)
 self.assertNotEqual(exitcode, 0)
@@ -211,6 +221,19 @@
 'Segmentation fault',
 filename=filename)
 
+ def test_enable_fd(self):
+ with tempfile.TemporaryFile('wb+') as fp:
+ fd = fp.fileno()
+ self.check_fatal_error("""
+ import faulthandler
+ import sys
+ faulthandler.enable(%s)
+ faulthandler._sigsegv()
+ """ % fd,
+ 4,
+ 'Segmentation fault',
+ fd=fd)
+
 def test_enable_single_thread(self):
 self.check_fatal_error("""
 import faulthandler
@@ -297,7 +320,7 @@
 output = subprocess.check_output(args, env=env)
 self.assertEqual(output.rstrip(), b"True")
 
- def check_dump_traceback(self, filename):
+ def check_dump_traceback(self, *, filename=None, fd=None):
 """
 Explicitly call dump_traceback() function and check its output.
 Raise an error if the output doesn't match the expected format.
@@ -305,10 +328,16 @@
 code = """
 import faulthandler
 
+ filename = {filename!r}
+ fd = {fd}
+
 def funcB():
- if {has_filename}:
- with open({filename}, "wb") as fp:
+ if filename:
+ with open(filename, "wb") as fp:
 faulthandler.dump_traceback(fp, all_threads=False)
+ elif fd is not None:
+ faulthandler.dump_traceback(fd,
+ all_threads=False)
 else:
 faulthandler.dump_traceback(all_threads=False)
 
@@ -318,29 +347,35 @@
 funcA()
 """
 code = code.format(
- filename=repr(filename),
- has_filename=bool(filename),
+ filename=filename,
+ fd=fd,
 )
 if filename:
- lineno = 6
+ lineno = 9
+ elif fd is not None:
+ lineno = 12
 else:
- lineno = 8
+ lineno = 14
 expected = [
 'Stack (most recent call first):',
 ' File "<string>", line %s in funcB' % lineno,
- ' File "<string>", line 11 in funcA',
- ' File "<string>", line 13 in <module>'
+ ' File "<string>", line 17 in funcA',
+ ' File "<string>", line 19 in <module>'
 ]
- trace, exitcode = self.get_output(code, filename)
+ trace, exitcode = self.get_output(code, filename, fd)
 self.assertEqual(trace, expected)
 self.assertEqual(exitcode, 0)
 
 def test_dump_traceback(self):
- self.check_dump_traceback(None)
+ self.check_dump_traceback()
 
 def test_dump_traceback_file(self):
 with temporary_filename() as filename:
- self.check_dump_traceback(filename)
+ self.check_dump_traceback(filename=filename)
+
+ def test_dump_traceback_fd(self):
+ with tempfile.TemporaryFile('wb+') as fp:
+ self.check_dump_traceback(fd=fp.fileno())
 
 def test_truncate(self):
 maxlen = 500
@@ -433,7 +468,10 @@
 with temporary_filename() as filename:
 self.check_dump_traceback_threads(filename)
 
- def _check_dump_traceback_later(self, repeat, cancel, filename, loops):
+ @unittest.skipIf(not hasattr(faulthandler, 'dump_traceback_later'),
+ 'need faulthandler.dump_traceback_later()')
+ def check_dump_traceback_later(self, repeat=False, cancel=False, loops=1,
+ *, filename=None, fd=None):
 """
 Check how many times the traceback is written in timeout x 2.5 seconds,
 or timeout x 3.5 seconds if cancel is True: 1, 2 or 3 times depending
@@ -445,6 +483,14 @@
 code = """
 import faulthandler
 import time
+ import sys
+
+ timeout = {timeout}
+ repeat = {repeat}
+ cancel = {cancel}
+ loops = {loops}
+ filename = {filename!r}
+ fd = {fd}
 
 def func(timeout, repeat, cancel, file, loops):
 for loop in range(loops):
@@ -454,16 +500,14 @@
 time.sleep(timeout * 5)
 faulthandler.cancel_dump_traceback_later()
 
- timeout = {timeout}
- repeat = {repeat}
- cancel = {cancel}
- loops = {loops}
- if {has_filename}:
- file = open({filename}, "wb")
+ if filename:
+ file = open(filename, "wb")
+ elif fd is not None:
+ file = sys.stderr.fileno()
 else:
 file = None
 func(timeout, repeat, cancel, file, loops)
- if file is not None:
+ if filename:
 file.close()
 """
 code = code.format(
@@ -471,8 +515,8 @@
 repeat=repeat,
 cancel=cancel,
 loops=loops,
- has_filename=bool(filename),
- filename=repr(filename),
+ filename=filename,
+ fd=fd,
 )
 trace, exitcode = self.get_output(code, filename)
 trace = '\n'.join(trace)
@@ -482,27 +526,12 @@
 if repeat:
 count *= 2
 header = r'Timeout \(%s\)!\nThread 0x[0-9a-f]+ \(most recent call first\):\n' % timeout_str
- regex = expected_traceback(9, 20, header, min_count=count)
+ regex = expected_traceback(17, 26, header, min_count=count)
 self.assertRegex(trace, regex)
 else:
 self.assertEqual(trace, '')
 self.assertEqual(exitcode, 0)
 
- @unittest.skipIf(not hasattr(faulthandler, 'dump_traceback_later'),
- 'need faulthandler.dump_traceback_later()')
- def check_dump_traceback_later(self, repeat=False, cancel=False,
- file=False, twice=False):
- if twice:
- loops = 2
- else:
- loops = 1
- if file:
- with temporary_filename() as filename:
- self._check_dump_traceback_later(repeat, cancel,
- filename, loops)
- else:
- self._check_dump_traceback_later(repeat, cancel, None, loops)
-
 def test_dump_traceback_later(self):
 self.check_dump_traceback_later()
 
@@ -513,15 +542,20 @@
 self.check_dump_traceback_later(cancel=True)
 
 def test_dump_traceback_later_file(self):
- self.check_dump_traceback_later(file=True)
+ with temporary_filename() as filename:
+ self.check_dump_traceback_later(filename=filename)
+
+ def test_dump_traceback_later_fd(self):
+ with tempfile.TemporaryFile('wb+') as fp:
+ self.check_dump_traceback_later(fd=fp.fileno())
 
 def test_dump_traceback_later_twice(self):
- self.check_dump_traceback_later(twice=True)
+ self.check_dump_traceback_later(loops=2)
 
 @unittest.skipIf(not hasattr(faulthandler, "register"),
 "need faulthandler.register")
 def check_register(self, filename=False, all_threads=False,
- unregister=False, chain=False):
+ unregister=False, chain=False, fd=None):
 """
 Register a handler displaying the traceback on a user signal. Raise the
 signal and check the written traceback.
@@ -537,6 +571,13 @@
 import signal
 import sys
 
+ all_threads = {all_threads}
+ signum = {signum}
+ unregister = {unregister}
+ chain = {chain}
+ filename = {filename!r}
+ fd = {fd}
+
 def func(signum):
 os.kill(os.getpid(), signum)
 
@@ -544,19 +585,16 @@
 handler.called = True
 handler.called = False
 
- exitcode = 0
- signum = {signum}
- unregister = {unregister}
- chain = {chain}
-
- if {has_filename}:
- file = open({filename}, "wb")
+ if filename:
+ file = open(filename, "wb")
+ elif fd is not None:
+ file = sys.stderr.fileno()
 else:
 file = None
 if chain:
 signal.signal(signum, handler)
 faulthandler.register(signum, file=file,
- all_threads={all_threads}, chain={chain})
+ all_threads=all_threads, chain={chain})
 if unregister:
 faulthandler.unregister(signum)
 func(signum)
@@ -567,17 +605,19 @@
 output = sys.stderr
 print("Error: signal handler not called!", file=output)
 exitcode = 1
- if file is not None:
+ else:
+ exitcode = 0
+ if filename:
 file.close()
 sys.exit(exitcode)
 """
 code = code.format(
- filename=repr(filename),
- has_filename=bool(filename),
 all_threads=all_threads,
 signum=signum,
 unregister=unregister,
 chain=chain,
+ filename=filename,
+ fd=fd,
 )
 trace, exitcode = self.get_output(code, filename)
 trace = '\n'.join(trace)
@@ -586,7 +626,7 @@
 regex = 'Current thread XXX \(most recent call first\):\n'
 else:
 regex = 'Stack \(most recent call first\):\n'
- regex = expected_traceback(7, 28, regex)
+ regex = expected_traceback(14, 32, regex)
 self.assertRegex(trace, regex)
 else:
 self.assertEqual(trace, '')
@@ -605,6 +645,10 @@
 with temporary_filename() as filename:
 self.check_register(filename=filename)
 
+ def test_register_fd(self):
+ with tempfile.TemporaryFile('wb+') as fp:
+ self.check_register(fd=fp.fileno())
+
 def test_register_threads(self):
 self.check_register(all_threads=True)
 
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -18,6 +18,10 @@
 Library
 -------
 
+- Issue #23566: enable(), register(), dump_traceback() and
+ dump_traceback_later() functions of faulthandler now accept file
+ descriptors. Patch by Wei Wu.
+
 - Issue #22928: Disabled HTTP header injections in http.client.
 Original patch by Demian Brecht.
 
diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c
--- a/Modules/faulthandler.c
+++ b/Modules/faulthandler.c
@@ -133,32 +133,46 @@
 call its flush() method.
 
 If file is NULL or Py_None, use sys.stderr as the new file.
+ If file is an integer, it will be treated as file descriptor.
 
- On success, return the new file and write the file descriptor into *p_fd.
- On error, return NULL. */
+ On success, return the file descriptor and write the new file into *file_ptr.
+ On error, return -1. */
 
-static PyObject*
-faulthandler_get_fileno(PyObject *file, int *p_fd)
+static int
+faulthandler_get_fileno(PyObject **file_ptr)
 {
 PyObject *result;
 long fd_long;
 int fd;
+ PyObject *file = *file_ptr;
 
 if (file == NULL || file == Py_None) {
 file = _PySys_GetObjectId(&PyId_stderr);
 if (file == NULL) {
 PyErr_SetString(PyExc_RuntimeError, "unable to get sys.stderr");
- return NULL;
+ return -1;
 }
 if (file == Py_None) {
 PyErr_SetString(PyExc_RuntimeError, "sys.stderr is None");
- return NULL;
+ return -1;
 }
 }
+ else if (PyLong_Check(file)) {
+ fd = _PyLong_AsInt(file);
+ if (fd == -1 && PyErr_Occurred())
+ return -1;
+ if (fd < 0 || !_PyVerify_fd(fd)) {
+ PyErr_SetString(PyExc_ValueError,
+ "file is not a valid file descripter");
+ return -1;
+ }
+ *file_ptr = NULL;
+ return fd;
+ }
 
 result = _PyObject_CallMethodId(file, &PyId_fileno, "");
 if (result == NULL)
- return NULL;
+ return -1;
 
 fd = -1;
 if (PyLong_Check(result)) {
@@ -171,7 +185,7 @@
 if (fd == -1) {
 PyErr_SetString(PyExc_RuntimeError,
 "file.fileno() is not a valid file descriptor");
- return NULL;
+ return -1;
 }
 
 result = _PyObject_CallMethodId(file, &PyId_flush, "");
@@ -181,8 +195,8 @@
 /* ignore flush() error */
 PyErr_Clear();
 }
- *p_fd = fd;
- return file;
+ *file_ptr = file;
+ return fd;
 }
 
 /* Get the state of the current thread: only call this function if the current
@@ -215,8 +229,8 @@
 &file, &all_threads))
 return NULL;
 
- file = faulthandler_get_fileno(file, &fd);
- if (file == NULL)
+ fd = faulthandler_get_fileno(&file);
+ if (fd < 0)
 return NULL;
 
 tstate = get_thread_state();
@@ -339,8 +353,8 @@
 "|Oi:enable", kwlist, &file, &all_threads))
 return NULL;
 
- file = faulthandler_get_fileno(file, &fd);
- if (file == NULL)
+ fd = faulthandler_get_fileno(&file);
+ if (fd < 0)
 return NULL;
 
 tstate = get_thread_state();
@@ -348,7 +362,7 @@
 return NULL;
 
 Py_XDECREF(fatal_error.file);
- Py_INCREF(file);
+ Py_XINCREF(file);
 fatal_error.file = file;
 fatal_error.fd = fd;
 fatal_error.all_threads = all_threads;
@@ -553,8 +567,8 @@
 if (tstate == NULL)
 return NULL;
 
- file = faulthandler_get_fileno(file, &fd);
- if (file == NULL)
+ fd = faulthandler_get_fileno(&file);
+ if (fd < 0)
 return NULL;
 
 /* format the timeout */
@@ -567,7 +581,7 @@
 cancel_dump_traceback_later();
 
 Py_XDECREF(thread.file);
- Py_INCREF(file);
+ Py_XINCREF(file);
 thread.file = file;
 thread.fd = fd;
 thread.timeout_us = timeout_us;
@@ -737,8 +751,8 @@
 if (tstate == NULL)
 return NULL;
 
- file = faulthandler_get_fileno(file, &fd);
- if (file == NULL)
+ fd = faulthandler_get_fileno(&file);
+ if (fd < 0)
 return NULL;
 
 if (user_signals == NULL) {
@@ -760,7 +774,7 @@
 }
 
 Py_XDECREF(user->file);
- Py_INCREF(file);
+ Py_XINCREF(file);
 user->file = file;
 user->fd = fd;
 user->all_threads = all_threads;
-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list

AltStyle によって変換されたページ (->オリジナル) /