Skip to content

bpo-1230540: Add threading.excepthook() #13515

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 27, 2019
Merged

bpo-1230540: Add threading.excepthook() #13515

merged 3 commits into from
May 27, 2019

Conversation

vstinner
Copy link
Member

@vstinner vstinner commented May 23, 2019

Add a new threading.excepthook() function which handles uncaught
Thread.run() exception. It can be overridden to control how uncaught
exceptions are handled.

threading.ExceptHookArgs is not documented on purpose: it should not
be used directly.

  • threading.excepthook() and threading.ExceptHookArgs.
  • Add _PyErr_Display(): similar to PyErr_Display(), but accept a
    'file' parameter.
  • Add _thread._excepthook(): C implementation of the exception hook
    calling _PyErr_Display().
  • Add _thread._ExceptHookArgs: structseq type.
  • Add threading._invoke_excepthook_wrapper() which handles the gory
    details to ensure that everything remains alive during Python
    shutdown.
  • Add unit tests.

https://bugs.python.org/issue1230540

@vstinner
Copy link
Member Author

cc @vlasovskikh

@vstinner
Copy link
Member Author

See PR #8610 for a different approach: reuse sys.excepthook.

@vstinner
Copy link
Member Author

In my current implementation, I chose to continue to rely on t_bootstrap() of Modules/_threadmodule.c to log exception in threading.excepthook().

Maybe another option would be to attempt to reuse sys.excepthook() to log threading.excepthook() new failure?

Maybe the best option would be to later reimplement threading.excepthook() in C which reduces a lot the risk of error during Python shutdown. In C, it's more reliable to call functions during Python shutdown. Especially functions which don't rely on Python modules, but only access the interpreter state, thread state and the current exception.

I prefer to start with a simple PR to introduce the new API, and later experiment the different options to make the code even more reliable.

@vstinner
Copy link
Member Author

I rewrote my PR, see the details in the issue: https://bugs.python.org/issue1230540#msg343443

@vstinner
Copy link
Member Author

Oops, I didn't revert properly my change which added "stderr" into arguments passed to threading.excepthook(). It's now fixed.

@vstinner
Copy link
Member Author

@serhiy-storchaka @pablogsal @pitrou: Would you mind to review the updated PR? See https://bugs.python.org/issue1230540 for the discussion.

@pitrou
Copy link
Member

pitrou commented May 25, 2019

From the doc: Handle uncaught :func:``Thread.run`` exception.

What if some C extension wants to report uncaught exceptions in C-created threads? Does it have to create a dummy Thread object?

For example, how about threads created by _thread.start_new_thread?

@vstinner
Copy link
Member Author

vstinner commented May 25, 2019

I modified my PR to use the threading identifier (threading.get_ident()) as the thread name if args.thread is None.

@vstinner
Copy link
Member Author

As you can see in test_excepthook_thread_None(), it's not possible to call directly threading.excepthook() directly only using the public API: threading._make_excepthook_args() is needed.

args = threading._make_excepthook_args([*sys.exc_info(), None])

Should it be made public? Maybe as threading.ExceptHookArgs?

Maybe it can be made public but not documented?

Add a new threading.excepthook() function which handles uncaught
Thread.run() exception. It can be overridden to control how uncaught
exceptions are handled.

threading.ExceptHookArgs is not documented on purpose: it should not
be used directly.

* threading.excepthook() and threading.ExceptHookArgs.
* Add _PyErr_Display(): similar to PyErr_Display(), but accept a
  'file' parameter.
* Add _thread._excepthook(): C implementation of the exception hook
  calling _PyErr_Display().
* Add _thread._ExceptHookArgs: structseq type.
* Add threading._invoke_excepthook_wrapper() which handles the gory
  details to ensure that everything remains alive during Python
  shutdown.
* Add unit tests.
@vstinner
Copy link
Member Author

I squashed my commits, rebased my PR on master and I renamed _make_excepthook_args as threading.ExceptHookArgs.

I chose to not document ExceptHookArgs. In the Python implementation, threading.ExceptHookArgs is a wrapper to threading._ExceptHookArgs: threading._ExceptHookArgs constructor takes N arguments, whereas ExceptHookArgs takes 1 argument.

Using threading.ExceptHookArgs, it's now possible to call directly threading.excepthook(threading.ExceptHook(...)) to log an error from a thread not created using threading.Thread, but using directly _thread.start_new_thread.

@pitrou: Does it look better to you?

@vstinner
Copy link
Member Author

I plan to merge this PR wednesday (in 2 days).

I don't see a strong opposition, and nobody came up with a working implementation for the idea of reusing sys.excepthook.

There is PR #8610 but I dislike it: threading.Thread doesn't call sys.excepthook to handle run() exception by default, it only calls sys.excepthook if it's overridden. Moreover, when sys.excepthook is called, the hook doesn't get access to the thread object :-(
https://bugs.python.org/issue1230540#msg343260

from traceback import print_exception as _print_exception
from collections import namedtuple

_ExceptHookArgs = namedtuple(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is namedtuple for possible future extending a list of provided arguments?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly. See sys.unraisablehook: I used the same approach and today I added a new "err_msg" attribute ;-)

@@ -953,10 +953,11 @@ print_exception_recursive(PyObject *f, PyObject *value, PyObject *seen)
}

void
PyErr_Display(PyObject *exception, PyObject *value, PyObject *tb)
_PyErr_Display(PyObject *file, PyObject *exception, PyObject *value, PyObject *tb)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make it public maybe? PyErr_DisplayToFile or something like this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to not add a new function to the public C API unless there is a clear need from users. In the past, the C API evolved organically and now it's a mess because nobody knows what is really used or not :-(

Copy link
Member

@pitrou pitrou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks mostly good to me. Some comments below.

@bedevere-bot
Copy link

When you're done making the requested changes, leave the comment: I have made the requested changes; please review again.

@vstinner
Copy link
Member Author

I have made the requested changes; please review again.

@bedevere-bot
Copy link

Thanks for making the requested changes!

@pitrou: please review the changes made to this pull request.

@vstinner vstinner merged commit cd590a7 into python:master May 27, 2019
@vstinner vstinner deleted the threading_excepthook branch May 27, 2019 22:45
@vstinner
Copy link
Member Author

Thanks for the review @pitrou!

@vstinner
Copy link
Member Author

For example, how about threads created by _thread.start_new_thread?

I created https://bugs.python.org/issue37076 for this specific function ;-)

DinoV pushed a commit to DinoV/cpython that referenced this pull request Jan 14, 2020
Add a new threading.excepthook() function which handles uncaught
Thread.run() exception. It can be overridden to control how uncaught
exceptions are handled.

threading.ExceptHookArgs is not documented on purpose: it should not
be used directly.

* threading.excepthook() and threading.ExceptHookArgs.
* Add _PyErr_Display(): similar to PyErr_Display(), but accept a
  'file' parameter.
* Add _thread._excepthook(): C implementation of the exception hook
  calling _PyErr_Display().
* Add _thread._ExceptHookArgs: structseq type.
* Add threading._invoke_excepthook_wrapper() which handles the gory
  details to ensure that everything remains alive during Python
  shutdown.
* Add unit tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants