eric6/DebugClients/Python/ThreadExtension.py

branch
maintenance
changeset 8043
0acf98cd089a
parent 7924
8a96736d465e
parent 7986
2971d5d19951
child 8273
698ae46f40a4
--- a/eric6/DebugClients/Python/ThreadExtension.py	Sun Jan 17 13:53:08 2021 +0100
+++ b/eric6/DebugClients/Python/ThreadExtension.py	Mon Feb 01 10:38:16 2021 +0100
@@ -7,9 +7,8 @@
 Module implementing an import hook patching thread modules to get debugged too.
 """
 
-import os.path
+import os
 import sys
-import importlib
 
 import _thread
 import threading
@@ -23,7 +22,7 @@
     """
     Class implementing the thread support for the debugger.
     
-    Provides methods for intercepting thread creation, retriving the running
+    Provides methods for intercepting thread creation, retrieving the running
     threads and their name and state.
     """
     def __init__(self):
@@ -31,11 +30,7 @@
         Constructor
         """
         self.threadNumber = 1
-        self.enableImportHooks = True
         self._original_start_new_thread = None
-        self.threadingAttached = False
-        self.qtThreadAttached = False
-        self.greenlet = False
         
         self.clientLock = threading.RLock()
         
@@ -49,12 +44,6 @@
         
         # special objects representing the main scripts thread and frame
         self.mainThread = self
-        
-        # reset already imported thread module to apply hooks at next import
-        del sys.modules['_thread']
-        del sys.modules['threading']
-        
-        sys.meta_path.insert(0, self)
 
     def attachThread(self, target=None, args=None, kwargs=None,
                      mainThread=False):
@@ -121,10 +110,7 @@
         @return flag indicating successful locking
         @rtype bool
         """
-        if blocking:
-            return self.clientLock.acquire()
-        else:
-            return self.clientLock.acquire(blocking)
+        return self.clientLock.acquire(blocking)
     
     def unlockClient(self):
         """
@@ -132,7 +118,7 @@
         """
         try:
             self.clientLock.release()
-        except AssertionError:
+        except RuntimeError:
             pass
     
     def setCurrentThread(self, threadId):
@@ -156,27 +142,23 @@
         Public method to send the list of threads.
         """
         self.updateThreadList()
+        
         threadList = []
-        if len(self.threads) > 1:
-            currentId = _thread.get_ident()
-            # update thread names set by user (threading.setName)
-            threadNames = {t.ident: t.getName() for t in threading.enumerate()}
+        currentId = _thread.get_ident()
+        # update thread names set by user (threading.setName)
+        threadNames = {t.ident: t.getName() for t in threading.enumerate()}
+        
+        for threadId, thd in self.threads.items():
+            d = {"id": threadId}
+            try:
+                d["name"] = threadNames.get(threadId, thd.name)
+                d["broken"] = thd.isBroken
+                d["except"] = thd.isException
+            except Exception:
+                d["name"] = 'UnknownThread'
+                d["broken"] = False
+                d["except"] = False
             
-            for threadId, thd in self.threads.items():
-                d = {"id": threadId}
-                try:
-                    d["name"] = threadNames.get(threadId, thd.name)
-                    d["broken"] = thd.isBroken
-                except Exception:
-                    d["name"] = 'UnknownThread'
-                    d["broken"] = False
-                
-                threadList.append(d)
-        else:
-            currentId = -1
-            d = {"id": -1}
-            d["name"] = "MainThread"
-            d["broken"] = self.isBroken
             threadList.append(d)
         
         self.sendJsonCommand("ResponseThreadList", {
@@ -237,198 +219,193 @@
         self.threads = {id_: thrd for id_, thrd in self.threads.items()
                         if id_ in frames}
     
-    def find_module(self, fullname, path=None):
-        """
-        Public method returning the module loader.
-        
-        @param fullname name of the module to be loaded
-        @type str
-        @param path path to resolve the module name
-        @type str
-        @return module loader object
-        @rtype object
+    #######################################################################
+    ## Methods below deal with patching various modules to support
+    ## debugging of threads.
+    #######################################################################
+    
+    def patchPyThread(self, module):
         """
-        if fullname in sys.modules or not self.debugging:
-            return None
+        Public method to patch Python _thread (Python3) and thread (Python2)
+        modules.
         
-        if fullname in ['_thread', 'PyQt5.QtCore', 'PySide2.QtCore',
-                        'greenlet', 'threading'
-                        ] and self.enableImportHooks:
-            # Disable hook to be able to import original module
-            self.enableImportHooks = False
-            return self
-        
-        return None
+        @param module reference to the imported module to be patched
+        @type module
+        """
+        # make thread hooks available to system
+        self._original_start_new_thread = module.start_new_thread
+        module.start_new_thread = self.attachThread
     
-    def load_module(self, fullname):
+    def patchGreenlet(self, module):
         """
-        Public method to load a module.
+        Public method to patch the 'greenlet' module.
         
-        @param fullname name of the module to be loaded
-        @type str
-        @return reference to the loaded module
-        @rtype module
+        @param module reference to the imported module to be patched
+        @type module
+        @return flag indicating that the module was processed
+        @rtype bool
         """
-        module = importlib.import_module(fullname)
-        sys.modules[fullname] = module
-        if (fullname == '_thread' and
-                self._original_start_new_thread is None):
-            # make thread hooks available to system
-            self._original_start_new_thread = module.start_new_thread
-            module.start_new_thread = self.attachThread
-
-        elif (fullname == 'greenlet' and self.greenlet is False):
-            # Check for greenlet.settrace
-            if hasattr(module, 'settrace'):
-                self.greenlet = True
-                DebugBase.pollTimerEnabled = False
+        # Check for greenlet.settrace
+        if hasattr(module, 'settrace'):
+            DebugBase.pollTimerEnabled = False
+            return True
+        return False
+    
+    def patchPyThreading(self, module):
+        """
+        Public method to patch the Python threading module.
+        
+        @param module reference to the imported module to be patched
+        @type module
+        """
+        # _debugClient as a class attribute can't be accessed in following
+        # class. Therefore we need a global variable.
+        _debugClient = self
         
-        # Add hook for threading.run()
-        elif (fullname == "threading" and self.threadingAttached is False):
-            self.threadingAttached = True
+        def _bootstrap(self, run):
+            """
+            Bootstrap for threading, which reports exceptions correctly.
+            
+            @param run the run method of threading.Thread
+            @type method pointer
+            """
+            newThread = DebugBase(_debugClient)
+            newThread.name = self.name
+            
+            _debugClient.threads[self.ident] = newThread
+            _debugClient.dumpThreadList()
             
-            # _debugClient as a class attribute can't be accessed in following
-            # class. Therefore we need a global variable.
-            _debugClient = self
-            
-            def _bootstrap(self, run):
-                """
-                Bootstrap for threading, which reports exceptions correctly.
-                
-                @param run the run method of threading.Thread
-                @type method pointer
+            # see DebugBase.bootstrap
+            sys.settrace(newThread.trace_dispatch)
+            try:
+                run()
+            except Exception:
+                excinfo = sys.exc_info()
+                newThread.user_exception(excinfo, True)
+            finally:
+                sys.settrace(None)
+                _debugClient.dumpThreadList()
+        
+        class ThreadWrapper(module.Thread):
+            """
+            Wrapper class for threading.Thread.
+            """
+            def __init__(self, *args, **kwargs):
                 """
-                newThread = DebugBase(_debugClient)
-                _debugClient.threads[self.ident] = newThread
-                newThread.name = self.name
-                # see DebugBase.bootstrap
-                sys.settrace(newThread.trace_dispatch)
-                try:
-                    run()
-                except Exception:
-                    excinfo = sys.exc_info()
-                    newThread.user_exception(excinfo, True)
-                finally:
-                    sys.settrace(None)
-            
-            class ThreadWrapper(module.Thread):
-                """
-                Wrapper class for threading.Thread.
+                Constructor
                 """
-                def __init__(self, *args, **kwargs):
-                    """
-                    Constructor
-                    """
-                    # Overwrite the provided run method with our own, to
-                    # intercept the thread creation by threading.Thread
-                    self.run = lambda s=self, run=self.run: _bootstrap(s, run)
-                    
-                    super(ThreadWrapper, self).__init__(*args, **kwargs)
+                # Overwrite the provided run method with our own, to
+                # intercept the thread creation by threading.Thread
+                self.run = lambda s=self, run=self.run: _bootstrap(s, run)
+                
+                super(ThreadWrapper, self).__init__(*args, **kwargs)
+        
+        module.Thread = ThreadWrapper
+        
+        # Special handling of threading.(_)Timer
+        timer = module.Timer
             
-            module.Thread = ThreadWrapper
-            
-            # Special handling of threading.(_)Timer
-            timer = module.Timer
-                
-            class TimerWrapper(timer, ThreadWrapper):
+        class TimerWrapper(timer, ThreadWrapper):
+            """
+            Wrapper class for threading.(_)Timer.
+            """
+            def __init__(self, interval, function, *args, **kwargs):
                 """
-                Wrapper class for threading.(_)Timer.
+                Constructor
                 """
-                def __init__(self, interval, function, *args, **kwargs):
-                    """
-                    Constructor
-                    """
-                    super(TimerWrapper, self).__init__(
-                        interval, function, *args, **kwargs)
-            
-            module.Timer = TimerWrapper
+                super(TimerWrapper, self).__init__(
+                    interval, function, *args, **kwargs)
         
-            # Special handling of threading._DummyThread
-            class DummyThreadWrapper(module._DummyThread, ThreadWrapper):
-                """
-                Wrapper class for threading._DummyThread.
+        module.Timer = TimerWrapper
+    
+        # Special handling of threading._DummyThread
+        class DummyThreadWrapper(module._DummyThread, ThreadWrapper):
+            """
+            Wrapper class for threading._DummyThread.
+            """
+            def __init__(self, *args, **kwargs):
                 """
-                def __init__(self, *args, **kwargs):
-                    """
-                    Constructor
-                    """
-                    super(DummyThreadWrapper, self).__init__(*args, **kwargs)
-            
-            module._DummyThread = DummyThreadWrapper
+                Constructor
+                """
+                super(DummyThreadWrapper, self).__init__(*args, **kwargs)
+        
+        module._DummyThread = DummyThreadWrapper
+    
+    def patchQThread(self, module):
+        """
+        Public method to patch the QtCore module's QThread.
         
-        # Add hook for *.QThread
-        elif (fullname in ['PyQt5.QtCore', 'PySide2.QtCore'] and
-              self.qtThreadAttached is False):
-            self.qtThreadAttached = True
-            # _debugClient as a class attribute can't be accessed in following
-            # class. Therefore we need a global variable.
-            _debugClient = self
+        @param module reference to the imported module to be patched
+        @type module
+        """
+        # _debugClient as a class attribute can't be accessed in following
+        # class. Therefore we need a global variable.
+        _debugClient = self
 
-            def _bootstrapQThread(self, run):
+        def _bootstrapQThread(self, run):
+            """
+            Bootstrap for QThread, which reports exceptions correctly.
+            
+            @param run the run method of *.QThread
+            @type method pointer
+            """
+            global _qtThreadNumber
+            
+            newThread = DebugBase(_debugClient)
+            ident = _thread.get_ident()
+            name = 'QtThread-{0}'.format(_qtThreadNumber)
+            
+            _qtThreadNumber += 1
+            
+            newThread.id = ident
+            newThread.name = name
+            
+            _debugClient.threads[ident] = newThread
+            _debugClient.dumpThreadList()
+            
+            # see DebugBase.bootstrap
+            sys.settrace(newThread.trace_dispatch)
+            try:
+                run()
+            except SystemExit:
+                # *.QThreads doesn't like SystemExit
+                pass
+            except Exception:
+                excinfo = sys.exc_info()
+                newThread.user_exception(excinfo, True)
+            finally:
+                sys.settrace(None)
+                _debugClient.dumpThreadList()
+    
+        class QThreadWrapper(module.QThread):
+            """
+            Wrapper class for *.QThread.
+            """
+            def __init__(self, *args, **kwargs):
                 """
-                Bootstrap for QThread, which reports exceptions correctly.
-                
-                @param run the run method of *.QThread
-                @type method pointer
+                Constructor
                 """
-                global _qtThreadNumber
-                
-                newThread = DebugBase(_debugClient)
-                ident = _thread.get_ident()
-                name = 'QtThread-{0}'.format(_qtThreadNumber)
-                
-                _qtThreadNumber += 1
-            
-                newThread.id = ident
-                newThread.name = name
+                # Overwrite the provided run method with our own, to
+                # intercept the thread creation by Qt
+                self.run = lambda s=self, run=self.run: (
+                    _bootstrapQThread(s, run))
                 
-                _debugClient.threads[ident] = newThread
-                
-                # see DebugBase.bootstrap
-                sys.settrace(newThread.trace_dispatch)
-                try:
-                    run()
-                except SystemExit:
-                    # *.QThreads doesn't like SystemExit
-                    pass
-                except Exception:
-                    excinfo = sys.exc_info()
-                    newThread.user_exception(excinfo, True)
-                finally:
-                    sys.settrace(None)
+                super(QThreadWrapper, self).__init__(*args, **kwargs)
         
-            class QThreadWrapper(module.QThread):
+        class QRunnableWrapper(module.QRunnable):
+            """
+            Wrapper class for *.QRunnable.
+            """
+            def __init__(self, *args, **kwargs):
                 """
-                Wrapper class for *.QThread.
+                Constructor
                 """
-                def __init__(self, *args, **kwargs):
-                    """
-                    Constructor
-                    """
-                    # Overwrite the provided run method with our own, to
-                    # intercept the thread creation by Qt
-                    self.run = lambda s=self, run=self.run: (
-                        _bootstrapQThread(s, run))
-                    
-                    super(QThreadWrapper, self).__init__(*args, **kwargs)
-            
-            class QRunnableWrapper(module.QRunnable):
-                """
-                Wrapper class for *.QRunnable.
-                """
-                def __init__(self, *args, **kwargs):
-                    """
-                    Constructor
-                    """
-                    # Overwrite the provided run method with our own, to
-                    # intercept the thread creation by Qt
-                    self.run = lambda s=self, run=self.run: (
-                        _bootstrapQThread(s, run))
-                    
-                    super(QRunnableWrapper, self).__init__(*args, **kwargs)
-            
-            module.QThread = QThreadWrapper
-            module.QRunnable = QRunnableWrapper
+                # Overwrite the provided run method with our own, to
+                # intercept the thread creation by Qt
+                self.run = lambda s=self, run=self.run: (
+                    _bootstrapQThread(s, run))
+                
+                super(QRunnableWrapper, self).__init__(*args, **kwargs)
         
-        self.enableImportHooks = True
-        return module
+        module.QThread = QThreadWrapper
+        module.QRunnable = QRunnableWrapper

eric ide

mercurial