diff -r 0469af231d22 Lib/test/test_faulthandler.py --- a/Lib/test/test_faulthandler.py Mon Mar 09 10:05:50 2015 -0700 +++ b/Lib/test/test_faulthandler.py Tue Mar 10 13:52:34 2015 +0800 @@ -297,18 +297,22 @@ 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, use_fd=False): """ Explicitly call dump_traceback() function and check its output. Raise an error if the output doesn't match the expected format. """ code = """ import faulthandler + import sys def funcB(): if {has_filename}: with open({filename}, "wb") as fp: faulthandler.dump_traceback(fp, all_threads=False) + elif {use_fd}: + faulthandler.dump_traceback(sys.stderr.fileno(), + all_threads=False) else: faulthandler.dump_traceback(all_threads=False) @@ -320,16 +324,19 @@ code = code.format( filename=repr(filename), has_filename=bool(filename), + use_fd=use_fd, ) if filename: - lineno = 6 + lineno = 7 + elif use_fd: + lineno = 10 else: - lineno = 8 + lineno = 12 expected = [ 'Stack (most recent call first):', ' File "", line %s in funcB' % lineno, - ' File "", line 11 in funcA', - ' File "", line 13 in ' + ' File "", line 15 in funcA', + ' File "", line 17 in ' ] trace, exitcode = self.get_output(code, filename) self.assertEqual(trace, expected) @@ -433,7 +440,8 @@ with temporary_filename() as filename: self.check_dump_traceback_threads(filename) - def _check_dump_traceback_later(self, repeat, cancel, filename, loops): + def _check_dump_traceback_later(self, repeat, cancel, + filename, loops, use_fd): """ 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,15 +453,18 @@ code = """ import faulthandler import time + import sys - def func(timeout, repeat, cancel, file, loops): + def func(timeout, repeat, cancel, file, loops, use_fd): + if use_fd: + file = sys.stderr.fileno() for loop in range(loops): faulthandler.dump_traceback_later(timeout, repeat=repeat, file=file) if cancel: faulthandler.cancel_dump_traceback_later() time.sleep(timeout * 5) faulthandler.cancel_dump_traceback_later() - + use_fd = {use_fd} timeout = {timeout} repeat = {repeat} cancel = {cancel} @@ -462,7 +473,7 @@ file = open({filename}, "wb") else: file = None - func(timeout, repeat, cancel, file, loops) + func(timeout, repeat, cancel, file, loops, use_fd) if file is not None: file.close() """ @@ -473,6 +484,7 @@ loops=loops, has_filename=bool(filename), filename=repr(filename), + use_fd =use_fd, ) trace, exitcode = self.get_output(code, filename) trace = '\n'.join(trace) @@ -482,7 +494,7 @@ 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(12, 23, header, min_count=count) self.assertRegex(trace, regex) else: self.assertEqual(trace, '') @@ -491,7 +503,7 @@ @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): + file=False, twice=False, use_fd=False): if twice: loops = 2 else: @@ -499,9 +511,11 @@ if file: with temporary_filename() as filename: self._check_dump_traceback_later(repeat, cancel, - filename, loops) + filename, loops, + use_fd=use_fd) else: - self._check_dump_traceback_later(repeat, cancel, None, loops) + self._check_dump_traceback_later(repeat, cancel, + None, loops, use_fd=use_fd) def test_dump_traceback_later(self): self.check_dump_traceback_later() @@ -521,7 +535,7 @@ @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, use_fd=False): """ Register a handler displaying the traceback on a user signal. Raise the signal and check the written traceback. @@ -545,17 +559,22 @@ handler.called = False exitcode = 0 + fd_or_file = None signum = {signum} unregister = {unregister} chain = {chain} + use_fd = {use_fd} if {has_filename}: file = open({filename}, "wb") + fd_or_file = file else: file = None + if use_fd: + fd_or_file = sys.stderr.fileno() if chain: signal.signal(signum, handler) - faulthandler.register(signum, file=file, + faulthandler.register(signum, file=fd_or_file, all_threads={all_threads}, chain={chain}) if unregister: faulthandler.unregister(signum) @@ -578,6 +597,7 @@ signum=signum, unregister=unregister, chain=chain, + use_fd=use_fd, ) trace, exitcode = self.get_output(code, filename) trace = '\n'.join(trace) @@ -586,7 +606,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(7, 33, regex) self.assertRegex(trace, regex) else: self.assertEqual(trace, '') @@ -636,6 +656,23 @@ with self.check_stderr_none(): faulthandler.register(signal.SIGUSR1) + def test_file_descriptor(self): + # Test enable with file descriptor. + with temporary_filename() as filename: + self.check_fatal_error(""" + import faulthandler + output = open({filename}, 'wb') + faulthandler.enable(output) + faulthandler._sigsegv() + """.format(filename=repr(filename)), + 4, + 'Segmentation fault', + filename=filename) + # Other tests. + self.check_register(use_fd=True) + self.check_dump_traceback_later(use_fd=True) + self.check_dump_traceback(None, use_fd=True) + if __name__ == "__main__": unittest.main() diff -r 0469af231d22 Modules/faulthandler.c --- a/Modules/faulthandler.c Mon Mar 09 10:05:50 2015 -0700 +++ b/Modules/faulthandler.c Tue Mar 10 13:52:34 2015 +0800 @@ -132,35 +132,51 @@ /* Get the file descriptor of a file by calling its fileno() method and then call its flush() method. - If file is NULL or Py_None, use sys.stderr as the new file. + If file is NULL or Py_None, *file_ptr will point to sys.stderr. + If file is integer, it will be treated as file descriptor, and *file_ptr + will be set to NULL. - On success, return the new file and write the file descriptor into *p_fd. - On error, return NULL. */ + On success, return 1 and write the file descriptor into *p_fd. + On error, return 0. */ -static PyObject* -faulthandler_get_fileno(PyObject *file, int *p_fd) +static int +faulthandler_get_fileno(PyObject **file_ptr, int *p_fd) { PyObject *result; long fd_long; - int fd; - + int fd = -1; + 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 0; } if (file == Py_None) { PyErr_SetString(PyExc_RuntimeError, "sys.stderr is None"); - return NULL; + return 0; } } + if (PyLong_Check(file)) { + fd_long = PyLong_AsLong(file); + if (0 <= fd_long && fd_long < INT_MAX) + fd = (int)fd_long; + if (fd == -1) { + PyErr_SetString(PyExc_RuntimeError, + "file is not a valid file descriptor"); + return 0; + } + *p_fd = fd; + *file_ptr = NULL; + return 1; + } + result = _PyObject_CallMethodId(file, &PyId_fileno, ""); if (result == NULL) - return NULL; + return 0; - fd = -1; if (PyLong_Check(result)) { fd_long = PyLong_AsLong(result); if (0 <= fd_long && fd_long < INT_MAX) @@ -171,7 +187,7 @@ if (fd == -1) { PyErr_SetString(PyExc_RuntimeError, "file.fileno() is not a valid file descriptor"); - return NULL; + return 0; } result = _PyObject_CallMethodId(file, &PyId_flush, ""); @@ -182,7 +198,8 @@ PyErr_Clear(); } *p_fd = fd; - return file; + *file_ptr = file; + return 1; } /* Get the state of the current thread: only call this function if the current @@ -215,8 +232,7 @@ &file, &all_threads)) return NULL; - file = faulthandler_get_fileno(file, &fd); - if (file == NULL) + if(!faulthandler_get_fileno(&file, &fd)) return NULL; tstate = get_thread_state(); @@ -339,8 +355,7 @@ "|Oi:enable", kwlist, &file, &all_threads)) return NULL; - file = faulthandler_get_fileno(file, &fd); - if (file == NULL) + if(!faulthandler_get_fileno(&file, &fd)) return NULL; tstate = get_thread_state(); @@ -348,7 +363,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 +568,7 @@ if (tstate == NULL) return NULL; - file = faulthandler_get_fileno(file, &fd); - if (file == NULL) + if (!faulthandler_get_fileno(&file, &fd)) 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,7 @@ if (tstate == NULL) return NULL; - file = faulthandler_get_fileno(file, &fd); - if (file == NULL) + if(!faulthandler_get_fileno(&file, &fd)) return NULL; if (user_signals == NULL) { @@ -760,7 +773,7 @@ } Py_XDECREF(user->file); - Py_INCREF(file); + Py_XINCREF(file); user->file = file; user->fd = fd; user->all_threads = all_threads;