diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index 13f0aff1cf99b6..6621874d607f42 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -81,6 +81,10 @@ Printing and clearing in which the unraisable exception occurred. If possible, the repr of *obj* will be printed in the warning message. + .. versionchanged:: 3.8 + The :option:`-X` ``abortunraisable`` command-line option causes this + function to abort the current process. + Raising exceptions ================== diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst index fd47ce2ab53849..728c0396de0772 100644 --- a/Doc/using/cmdline.rst +++ b/Doc/using/cmdline.rst @@ -446,6 +446,9 @@ Miscellaneous options * ``-X pycache_prefix=PATH`` enables writing ``.pyc`` files to a parallel tree rooted at the given directory instead of to the code tree. See also :envvar:`PYTHONPYCACHEPREFIX`. + * ``-X abortunraisable`` causes the :c:func:`PyErr_WriteUnraisable` + function to abort the current process. This is useful for debugging + purposes. It also allows passing arbitrary values and retrieving them through the :data:`sys._xoptions` dictionary. @@ -466,8 +469,9 @@ Miscellaneous options The ``-X importtime``, ``-X dev`` and ``-X utf8`` options. .. versionadded:: 3.8 - The ``-X pycache_prefix`` option. The ``-X dev`` option now logs - ``close()`` exceptions in :class:`io.IOBase` destructor. + The ``-X pycache_prefix`` and ``-X abortunraisable`` options. The + ``-X dev`` option now logs ``close()`` exceptions in the + :class:`io.IOBase` destructor. Options you shouldn't use diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index d6388f8faaba4f..c7505dc5e98ef9 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -206,6 +206,9 @@ Other Language Changes and Windows use this to properly terminate scripts in interactive sessions. (Contributed by Google via Gregory P. Smith in :issue:`1054041`.) +* The new :option:`-X` ``abortunraisable`` option causes the + :c:func:`PyErr_WriteUnraisable` C API function to abort the current process. + (Contributed by Zackery Spytz in :issue:`36829`.) New Modules =========== diff --git a/Include/cpython/coreconfig.h b/Include/cpython/coreconfig.h index 47a6baa1118fca..375c0b641df991 100644 --- a/Include/cpython/coreconfig.h +++ b/Include/cpython/coreconfig.h @@ -171,6 +171,7 @@ typedef struct { int import_time; /* PYTHONPROFILEIMPORTTIME, -X importtime */ int show_ref_count; /* -X showrefcount */ int show_alloc_count; /* -X showalloccount */ + int abort_unraisable; /* -X abortunraisable */ int dump_refs; /* PYTHONDUMPREFS */ int malloc_stats; /* PYTHONMALLOCSTATS */ diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 6cc9eb322e280b..797adff9b1e828 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -301,7 +301,7 @@ def _args_from_interpreter_flags(): if dev_mode: args.extend(('-X', 'dev')) for opt in ('faulthandler', 'tracemalloc', 'importtime', - 'showalloccount', 'showrefcount', 'utf8'): + 'showalloccount', 'showrefcount', 'utf8', 'abortunraisable'): if opt in xoptions: value = xoptions[opt] if value is True: diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index fdf5793789dfed..684c690d50d5f4 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -303,6 +303,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): 'import_time': 0, 'show_ref_count': 0, 'show_alloc_count': 0, + 'abort_unraisable': 0, 'dump_refs': 0, 'malloc_stats': 0, @@ -554,6 +555,7 @@ def test_init_from_config(self): 'import_time': 1, 'show_ref_count': 1, 'show_alloc_count': 1, + 'abort_unraisable': 1, 'malloc_stats': 1, 'stdio_encoding': 'iso8859-1', diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index cb664bab17109d..1f7fa5a0f18cda 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -495,6 +495,7 @@ def test_args_from_interpreter_flags(self): ['-X', 'importtime'], ['-X', 'showalloccount'], ['-X', 'showrefcount'], + ['-X', 'abortunraisable'], ['-X', 'tracemalloc'], ['-X', 'tracemalloc=3'], ): diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-05-07-14-16-10.bpo-36829.e35gtA.rst b/Misc/NEWS.d/next/Core and Builtins/2019-05-07-14-16-10.bpo-36829.e35gtA.rst new file mode 100644 index 00000000000000..4fd2fa7c8d25c8 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2019-05-07-14-16-10.bpo-36829.e35gtA.rst @@ -0,0 +1,2 @@ +Add a -X command-line option to abort the current process if +:c:func:`PyErr_WriteUnraisable` is called. diff --git a/Programs/_testembed.c b/Programs/_testembed.c index b12594799bfc93..6ddf08ce72acd5 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -405,6 +405,7 @@ static int test_init_from_config(void) config.show_ref_count = 1; config.show_alloc_count = 1; + config.abort_unraisable = 1; /* FIXME: test dump_refs: bpo-34223 */ putenv("PYTHONMALLOCSTATS=0"); diff --git a/Python/coreconfig.c b/Python/coreconfig.c index ac01712127ac0a..84db080a9aa79a 100644 --- a/Python/coreconfig.c +++ b/Python/coreconfig.c @@ -619,6 +619,7 @@ _PyCoreConfig_Copy(_PyCoreConfig *config, const _PyCoreConfig *config2) COPY_ATTR(import_time); COPY_ATTR(show_ref_count); COPY_ATTR(show_alloc_count); + COPY_ATTR(abort_unraisable); COPY_ATTR(dump_refs); COPY_ATTR(malloc_stats); @@ -721,6 +722,7 @@ _PyCoreConfig_AsDict(const _PyCoreConfig *config) SET_ITEM_INT(import_time); SET_ITEM_INT(show_ref_count); SET_ITEM_INT(show_alloc_count); + SET_ITEM_INT(abort_unraisable); SET_ITEM_INT(dump_refs); SET_ITEM_INT(malloc_stats); SET_ITEM_WSTR(filesystem_encoding); @@ -1507,6 +1509,9 @@ config_read(_PyCoreConfig *config, _PyPreCmdline *cmdline) if (config_get_xoption(config, L"showalloccount")) { config->show_alloc_count = 1; } + if (config_get_xoption(config, L"abortunraisable")) { + config->abort_unraisable = 1; + } err = config_read_complex_options(config); if (_Py_INIT_FAILED(err)) { diff --git a/Python/errors.c b/Python/errors.c index b8af1df4161d57..5f7ae70c7b3c05 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -1028,6 +1028,10 @@ PyErr_WriteUnraisable(PyObject *obj) Py_XDECREF(t); Py_XDECREF(v); Py_XDECREF(tb); + PyInterpreterState *interp = _PyInterpreterState_Get(); + if (interp->core_config.abort_unraisable) { + Py_FatalError("Unraisable exception"); + } PyErr_Clear(); /* Just in case */ }