Skip to content

Add threading.excepthook() to handle uncaught exceptions raised by Thread.run() #42148

Closed

Description

@ellisj
mannequin
BPO 1230540
Nosy @mwhudson, @tim-one, @ncoghlan, @pitrou, @vstinner, @merwok, @vlasovskikh, @serhiy-storchaka, @lazka, @AraHaan
PRs
  • bpo-1230540: Invoke custom sys.excepthook for threads in threading #8610
  • bpo-1230540: Add threading.excepthook() #13515
  • Files
  • fork_thread.py
  • sys_threading_excepthook.py
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = <Date 2019-05-27.22:53:59.194>
    created_at = <Date 2005-06-30.19:06:10.000>
    labels = ['interpreter-core', 'type-bug', '3.8']
    title = 'Add threading.excepthook() to handle uncaught exceptions raised by Thread.run()'
    updated_at = <Date 2019-05-28.10:20:57.279>
    user = 'https://bugs.python.org/ellisj'

    bugs.python.org fields:

    activity = <Date 2019-05-28.10:20:57.279>
    actor = 'vstinner'
    assignee = 'none'
    closed = True
    closed_date = <Date 2019-05-27.22:53:59.194>
    closer = 'vstinner'
    components = ['Interpreter Core']
    creation = <Date 2005-06-30.19:06:10.000>
    creator = 'ellisj'
    dependencies = []
    files = ['48351', '48354']
    hgrepos = []
    issue_num = 1230540
    keywords = ['patch']
    message_count = 42.0
    messages = ['25694', '25695', '25696', '62933', '91243', '91244', '272002', '272003', '272006', '288513', '288514', '297221', '302320', '302645', '322892', '322895', '322932', '323111', '324422', '324436', '343252', '343258', '343260', '343261', '343276', '343280', '343293', '343443', '343444', '343474', '343494', '343510', '343512', '343515', '343516', '343522', '343523', '343524', '343692', '343694', '343748', '343758']
    nosy_count = 15.0
    nosy_names = ['mwh', 'tim.peters', 'ncoghlan', 'ellisj', 'pitrou', 'vstinner', 'tiagoaoa', 'eric.araujo', 'undercoveridiot', 'vlasovskikh', 'serhiy.storchaka', 'lazka', 'Decorater', 'CyberJacob', 'Matt Groth']
    pr_nums = ['8610', '13515']
    priority = 'normal'
    resolution = 'fixed'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue1230540'
    versions = ['Python 3.8']

    Activity

    1. ellisj commented on Jun 30, 2005

      ellisjmannequin
      MannequinAuthor

      simple script to reproduce:

      import sys, threading
      
      def log_exception(*args):
          print 'got exception %s' % (args,)
      sys.excepthook = log_exception
      
      def foo():
          a = 1 / 0
      threading.Thread(target=foo).start()

      Note that a normal traceback is printed instead of "got
      exception."

    2. mwhudson commented on Jun 6, 2007

      @mwhudson

      I've just run into this, and it's very annoying. The stupid part is that threads started with the thread module don't have this problem, it's just a problem with threading.Thread()s trying to be too clever.

      I would vote for deleting all the code to do with exception printing in threading.py and letting the C machinery take care of it.

    3. ellisj commented on Jun 15, 2007

      ellisjmannequin
      MannequinAuthor

      Here is a workaround in the meantime:

      def install_thread_excepthook():
          """
          Workaround for sys.excepthook thread bug
          (https://sourceforge.net/tracker/?func=detail&atid=105470&aid=1230540&group_id=5470).
          Call once from __main__ before creating any threads.
          If using psyco, call psycho.cannotcompile(threading.Thread.run)
          since this replaces a new-style class method.
          """
          import sys
          run_old = threading.Thread.run
          def run(*args, **kwargs):
              try:
                  run_old(*args, **kwargs)
              except (KeyboardInterrupt, SystemExit):
                  raise
              except:
                  sys.excepthook(*sys.exc_info())
          threading.Thread.run = run
    4. tiagoaoa commented on Feb 24, 2008

      tiagoaoamannequin
      Mannequin

      I don't see it as a problem or as the threading module trying to be
      "clever". It clearly was a design choice to make the module have its own
      exception treatment in order to make it clear in which thread the
      exception occurred.
      IMHO the decision here should be to implement per thread excepthook's.

    5. undercoveridiot commented on Aug 3, 2009

      undercoveridiotmannequin
      Mannequin

      I found that the workaround suggested doesn't work when you have a
      subclass of threading.Thread and you want to catch everything in the
      module that contains the class to a common log.

      Say you have a module with a socket server that spawns a thread on
      accept and you want to log anything that tracebacks in the module. This
      seems to work:

      import sys
      import logging
      from functools import wraps
      
      def myExceptHook(type, value, tb):
          """ Redirect tracebacks to error log """
          import traceback
          rawreport = traceback.format_exception(type, value, tb)
          report = '\n'.join(rawreport)
          log.error(report)
      
      sys.excepthook = myExceptHook
          
      def use_my_excepthook(view):
          """ Redirect any unexpected tracebacks """ 
          @wraps(view)
          def run(*args, **kwargs):
              try:
                  return view(*args, **kwargs)
              except:
                  sys.excepthook(*sys.exc_info())
          return run

      Then in your thread subclass:

      class MyThread(threading.Thread):
          def __init__(self, socket_conn):
              threading.Thread.__init__(self)
              self.my_conn = socket_conn
      
          @use_my_excepthook
          def run(self):
              """ Do stuff """
    6. undercoveridiot commented on Aug 3, 2009

      undercoveridiotmannequin
      Mannequin

      Instead of using decorators, this is a slightly simpler modification to
      the proposed workaround that allows for any subclassed run method to
      also be caught.

      def installThreadExcepthook():
          """
          Workaround for sys.excepthook thread bug
          From
      http://spyced.blogspot.com/2007/06/workaround-for-sysexcepthook-bug.html
         
      (https://sourceforge.net/tracker/?func=detail&atid=105470&aid=1230540&group_id=5470).
          Call once from __main__ before creating any threads.
          If using psyco, call psyco.cannotcompile(threading.Thread.run)
          since this replaces a new-style class method.
          """
          init_old = threading.Thread.__init__
          def init(self, *args, **kwargs):
              init_old(self, *args, **kwargs)
              run_old = self.run
              def run_with_except_hook(*args, **kw):
                  try:
                      run_old(*args, **kw)
                  except (KeyboardInterrupt, SystemExit):
                      raise
                  except:
                      sys.excepthook(*sys.exc_info())
              self.run = run_with_except_hook
          threading.Thread.__init__ = init
    7. AraHaan commented on Aug 5, 2016

      AraHaanmannequin
      Mannequin

      I too agree that I hate the thread exceptions being printed in the console I would suggest python was to error log it all to a file instead (so it does not spam up the console). I get it a lot with websocket / opus errors and it is annoying because it does not cleanup ffmpeg for me.

    8. AraHaan commented on Aug 5, 2016

      AraHaanmannequin
      Mannequin

      personally these exceptions in console can be annoying to me as I hate seeing them.

      Exception in thread Thread-11:
      Traceback (most recent call last):
        File "threading.py", line 914, in _bootstrap_inner
        File "E:\Users\Elsword\Desktop\DecoraterBot\Async\test\resources\Dependencies\discord\voice_client.py", line 150, in run
          super().run()
        File "E:\Users\Elsword\Desktop\DecoraterBot\Async\test\resources\Dependencies\discord\voice_client.py", line 108, in run
          self.player(data)
        File "E:\Users\Elsword\Desktop\DecoraterBot\Async\test\resources\Dependencies\discord\voice_client.py", line 669, in play_audio
          sent = self.socket.sendto(packet, (self.endpoint_ip, self.voice_port))
      OSError: [WinError 10055] An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full
      
      Exception in thread Thread-21:
      Traceback (most recent call last):
        File "threading.py", line 914, in _bootstrap_inner
        File "E:\Users\Elsword\Desktop\DecoraterBot\Async\test\resources\Dependencies\discord\voice_client.py", line 150, in run
          super().run()
        File "E:\Users\Elsword\Desktop\DecoraterBot\Async\test\resources\Dependencies\discord\voice_client.py", line 108, in run
          self.player(data)
        File "E:\Users\Elsword\Desktop\DecoraterBot\Async\test\resources\Dependencies\discord\voice_client.py", line 669, in play_audio
          sent = self.socket.sendto(packet, (self.endpoint_ip, self.voice_port))
      OSError: [WinError 10038] An operation was attempted on something that is not a socket
    9. AraHaan commented on Aug 5, 2016

      AraHaanmannequin
      Mannequin

      Ok, so I just found out you can bypass thread exceptions by wraping the line that actually runs the threads in a try/except block and then using the logging module to log it to a file instead of the console.

    10. CyberJacob commented on Feb 24, 2017

      CyberJacobmannequin
      Mannequin

      Does this affect threads started with the multiprocessing library as well?

    11. CyberJacob commented on Feb 24, 2017

      CyberJacobmannequin
      Mannequin

      Just confirmed, this does affect multiprocessing. script to reproduce:

      import sys, multiprocessing
      
      def log_exception(*args):
          print 'got exception %s' % (args,)
      
      sys.excepthook = log_exception
      
      def foo():
          a = 1 / 0

      multiprocessing.Process(target=foo).start()

      again, just a normal traceback instead of "got exception"

    12. pitrou commented on Jun 28, 2017

      @pitrou
      Member

      Apparently the traceback module has been used from the start (since changset 7f5013a) to print the exception and not sys.excepthook. I presume this is an oversight, as I don't see any reason to prefer traceback here. Tim, any insight?

    13. 29 remaining items

    14. pitrou commented on May 25, 2019

      @pitrou
      Member

      Le 25/05/2019 à 23:09, STINNER Victor a écrit :

      I don't see the relationship between the API (signature) and the ability to set a per-thread hook.

      I'm not convinced that a per-thread hook is needed.

      Indeed, if you write your own Thread class, you can add a try...except
      in the Thread.run() method. You don't need a dedicated
      Thread.excepthook() method.

      The only way a per-thread hook could be useful is if you could set it
      *outside* of the Thread class (so not as a method), so that one can e.g.
      catch / report exceptions raised in threads launches by third-party
      libraries.

    15. vstinner commented on May 25, 2019

      @vstinner
      Member

      Indeed, if you write your own Thread class, you can add a try...except
      in the Thread.run() method. You don't need a dedicated
      Thread.excepthook() method.

      Exactly. You can already do you best in your run() method to handle exceptions.

      threading.excepthook is only there is everything else already failed.

      FYI in my implementation, if threading.excepthook raises a new exception, it's also handled... by sys.excepthook this time ;-)

      The only way a per-thread hook could be useful is if you could set it
      *outside* of the Thread class (so not as a method), so that one can e.g.
      catch / report exceptions raised in threads launches by third-party
      libraries.

      I discuss threading excepthook with Pablo and he asked me if it would be possible to have a different behavior depending if the thread is spawn by my application or by "third party code". Using threading.excepthook, you can mark your threads that you spawn directly using a specific name, a special attribute, or you may even track them in a list (maybe using weak references).

      If sys.excepthook is used to handle threading exceptions, you call threading.current_thread(), but then we come back to the issue to "dying" Python: exception which occurs late in Python finalization, when most modules are already cleared and import no longer works.

    16. vstinner commented on May 25, 2019

      @vstinner
      Member

      Antoine Pitrou:

      A Thread.excepthook() method does not allow to notify exceptions raised in C-created threads ("dummy threads").

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

    17. vstinner commented on May 25, 2019

      @vstinner
      Member

      In any case, I think the namedtuple / structseq solution is elegant, because we can add additional information later

      I used the same design for the new sys.unraisablehook in bpo-36829 and I'm already working on adding a new 'err_msg' field to the argument passed to this took: PR 13488 :-)

      I was trapped in the past when I had to modify warnings "hooks" (warnings.showwarning and warnings.formatwarning) when I had to add a new 'source' parameter. I had to write wrappers which are fragile, to keep the backward compatibility.

      (the user must only be careful not to use tuple unpacking)

      threading.excepthook doesn't mention the compatibility with tuple on purpose. It only documents attributes with their names.

    18. vstinner commented on May 27, 2019

      @vstinner
      Member

      New changeset cd590a7 by Victor Stinner in branch 'master':
      bpo-1230540: Add threading.excepthook() (GH-13515)
      cd590a7

    19. vstinner commented on May 27, 2019

      @vstinner
      Member

      It took 14 years, but this issue is now fixed ;-) I close it.

      Python 3.8 beta 1 will be released in a few days with threading.excepthook(): please test it to ensure that it works as you expected.

      As a follow-up, I created bpo-37069: "regrtest: log unraisable exceptions and uncaught thread exceptions".

    20. serhiy-storchaka commented on May 28, 2019

      @serhiy-storchaka
      Member

      If we want to support low-level threads created by start_new_thread() we should call excepthook() from t_bootstrap instead of Thread._bootstrap_inner. Otherwise implementing excepthook() as a Thread method would be more convenient.

    21. changed the title [-]sys.excepthook doesn't work in threads[/-] [+]Add threading.excepthook() to handle uncaught exceptions raised by Thread.run()[/+] on May 28, 2019
    22. changed the title [-]sys.excepthook doesn't work in threads[/-] [+]Add threading.excepthook() to handle uncaught exceptions raised by Thread.run()[/+] on May 28, 2019
    23. vstinner commented on May 28, 2019

      @vstinner
      Member

      If we want to support low-level threads created by start_new_thread() we should call excepthook() from t_bootstrap instead of Thread._bootstrap_inner. Otherwise implementing excepthook() as a Thread method would be more convenient.

      I created bpo-37076: "_thread.start_new_thread(): call sys.unraisablehook() to handle uncaught exceptions".

    24. transferred this issue fromon Apr 9, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    No one assigned

      Labels

      3.8 (EOL)end of lifeinterpreter-core(Objects, Python, Grammar, and Parser dirs)type-bugAn unexpected behavior, bug, or error

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions