Relocated some methods from Debug*Thread and the remaining modules into a new one. debugger speed

Thu, 06 Oct 2016 22:28:12 +0200

author
T.Rzepka <Tobias.Rzepka@gmail.com>
date
Thu, 06 Oct 2016 22:28:12 +0200
branch
debugger speed
changeset 5207
7283629b02c0
parent 5206
997064ba25d6
child 5208
aa8045780ce4

Relocated some methods from Debug*Thread and the remaining modules into a new one.

DebugClients/Python/DebugBase.py file | annotate | diff | comparison | revisions
DebugClients/Python/DebugClient.py file | annotate | diff | comparison | revisions
DebugClients/Python/DebugClientBase.py file | annotate | diff | comparison | revisions
DebugClients/Python/DebugClientThreads.py file | annotate | diff | comparison | revisions
DebugClients/Python/DebugThread.py file | annotate | diff | comparison | revisions
DebugClients/Python/ThreadExtension.py file | annotate | diff | comparison | revisions
eric6.e4p file | annotate | diff | comparison | revisions
--- a/DebugClients/Python/DebugBase.py	Tue Oct 04 22:38:29 2016 +0200
+++ b/DebugClients/Python/DebugBase.py	Thu Oct 06 22:28:12 2016 +0200
@@ -94,11 +94,6 @@
         self.returnframe = None
         self.stop_everywhere = False
         
-        # provide a hook to perform a hard breakpoint
-        # Use it like this:
-        # if hasattr(sys, 'breakpoint): sys.breakpoint()
-        sys.breakpoint = self.set_trace
-        
         self.__recursionDepth = -1
         self.setRecursionDepth(inspect.currentframe())
         
@@ -399,6 +394,26 @@
         sys.settrace(self.trace_dispatch)
         sys.setprofile(self._dbgClient.callTraceEnabled)
     
+    def bootstrap(self):
+        """
+        Public method to bootstrap the thread.
+        
+        It wraps the call to the user function to enable tracing
+        before hand.
+        """
+        try:
+            self._threadRunning = True
+            self.traceThread()
+            self._target(*self._args, **self._kwargs)
+        except SystemExit:
+            pass
+        finally:
+            self._threadRunning = False
+            self.quitting = True
+            self._dbgClient.threadTerminated(self)
+            sys.settrace(None)
+            sys.setprofile(None)
+    
     def run(self, cmd, globals=None, locals=None):
         """
         Public method to start a given command under debugger control.
@@ -668,7 +683,7 @@
             # Always show at least one stack frame, even if it's from eric.
             if stack and os.path.basename(fname).startswith(
                     ("DebugBase.py", "DebugClientBase.py",
-                    "ThreadExtension.py", "threading.py")):
+                     "ThreadExtension.py", "threading.py")):
                 break
             
             fline = fr.f_lineno
--- a/DebugClients/Python/DebugClient.py	Tue Oct 04 22:38:29 2016 +0200
+++ b/DebugClients/Python/DebugClient.py	Thu Oct 06 22:28:12 2016 +0200
@@ -4,14 +4,15 @@
 #
 
 """
-Module implementing a non-threaded variant of the debug client.
+Module implementing the standard debug client.
 """
 
 from DebugBase import DebugBase
-import DebugClientBase
+from DebugClientBase import DebugClientBase
+from ThreadExtension import ThreadExtension
 
 
-class DebugClient(DebugClientBase.DebugClientBase, DebugBase):
+class DebugClient(DebugClientBase, DebugBase, ThreadExtension):
     """
     Class implementing the client side of the debugger.
     
@@ -22,10 +23,12 @@
         """
         Constructor
         """
-        DebugClientBase.DebugClientBase.__init__(self)
+        DebugClientBase.__init__(self)
         
         DebugBase.__init__(self, self)
         
+        ThreadExtension.__init__(self)
+        
         self.variant = 'Standard'
 
 # We are normally called by the debugger to execute directly.
--- a/DebugClients/Python/DebugClientBase.py	Tue Oct 04 22:38:29 2016 +0200
+++ b/DebugClients/Python/DebugClientBase.py	Thu Oct 06 22:28:12 2016 +0200
@@ -188,17 +188,6 @@
         self.breakpoints = {}
         self.redirect = True
         self.__receiveBuffer = ""
-
-        # The next couple of members are needed for the threaded version.
-        # For this base class they contain static values for the non threaded
-        # debugger
-        
-        # dictionary of all threads running
-        self.threads = {}
-        
-        # the "current" thread, basically the thread we are at a breakpoint
-        # for.
-        self.currentThread = self
         
         # special objects representing the main scripts thread and frame
         self.mainThread = self
@@ -278,54 +267,7 @@
                     self.__coding = m.group(1)
                     return
             self.__coding = default
-
-    def attachThread(self, target=None, args=None, kwargs=None,
-                     mainThread=False):
-        """
-        Public method to setup a thread for DebugClient to debug.
         
-        If mainThread is True, then we are attaching to the already
-        started mainthread of the app and the rest of the args are ignored.
-        
-        @param target the start function of the target thread (i.e. the user
-            code)
-        @param args arguments to pass to target
-        @param kwargs keyword arguments to pass to target
-        @param mainThread True, if we are attaching to the already
-              started mainthread of the app
-        """
-        pass
-
-    def __dumpThreadList(self):
-        """
-        Private method to send the list of threads.
-        """
-        threadList = []
-        if self.threads and self.currentThread:
-            # indication for the threaded debugger
-            currentId = self.currentThread.get_ident()
-            for t in self.threads.values():
-                d = {}
-                d["id"] = t.get_ident()
-                d["name"] = t.get_name()
-                d["broken"] = t.isBroken
-                threadList.append(d)
-        else:
-            currentId = -1
-            d = {}
-            d["id"] = -1
-            d["name"] = "MainThread"
-            if hasattr(self, "isBroken"):
-                d["broken"] = self.isBroken
-            else:
-                d["broken"] = False
-            threadList.append(d)
-        
-        self.sendJsonCommand("ResponseThreadList", {
-            "currentID": currentId,
-            "threadList": threadList,
-        })
-    
     def raw_input(self, prompt, echo):
         """
         Public method to implement raw_input() / input() using the event loop.
@@ -458,7 +400,7 @@
                 params["scope"], params["filters"])
         
         elif method == "RequestThreadList":
-            self.__dumpThreadList()
+            self.dumpThreadList()
         
         elif method == "RequestThreadSet":
             if params["threadID"] in self.threads:
--- a/DebugClients/Python/DebugClientThreads.py	Tue Oct 04 22:38:29 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,201 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
-#
-
-"""
-Module implementing the multithreaded version of the debug client.
-"""
-
-import _thread
-import sys
-
-from DebugThread import DebugThread
-import DebugClientBase
-
-
-def _debugclient_start_new_thread(target, args, kwargs={}):
-    """
-    Module function used to allow for debugging of multiple threads.
-    
-    The way it works is that below, we reset _thread._start_new_thread to
-    this function object. Thus, providing a hook for us to see when
-    threads are started. From here we forward the request onto the
-    DebugClient which will create a DebugThread object to allow tracing
-    of the thread then start up the thread. These actions are always
-    performed in order to allow dropping into debug mode.
-    
-    See DebugClientThreads.attachThread and DebugThread.DebugThread in
-    DebugThread.py
-    
-    @param target the start function of the target thread (i.e. the user code)
-    @param args arguments to pass to target
-    @param kwargs keyword arguments to pass to target
-    @return The identifier of the created thread
-    """
-    if DebugClientBase.DebugClientInstance is not None:
-        return DebugClientBase.DebugClientInstance.attachThread(
-            target, args, kwargs)
-    else:
-        return _original_start_thread(target, args, kwargs)
-    
-# make _thread hooks available to system
-_original_start_thread = _thread.start_new_thread
-_thread.start_new_thread = _debugclient_start_new_thread
-
-# Note: import threading here AFTER above hook, as threading cache's
-#       thread._start_new_thread.
-from threading import RLock
-
-
-class DebugClientThreads(DebugClientBase.DebugClientBase):
-    """
-    Class implementing the client side of the debugger.
-
-    This variant of the debugger implements a threaded debugger client
-    by subclassing all relevant base classes.
-    """
-    def __init__(self):
-        """
-        Constructor
-        """
-        DebugClientBase.DebugClientBase.__init__(self)
-        
-        # protection lock for synchronization
-        self.clientLock = RLock()
-        
-        # the "current" thread, basically the thread we are at a breakpoint
-        # for.
-        self.currentThread = None
-        
-        # special objects representing the main scripts thread and frame
-        self.mainThread = None
-        self.mainFrame = None
-        
-        self.variant = 'Threaded'
-
-    def attachThread(self, target=None, args=None, kwargs=None,
-                     mainThread=False):
-        """
-        Public method to setup a thread for DebugClient to debug.
-        
-        If mainThread is True, then we are attaching to the already
-        started mainthread of the app and the rest of the args are ignored.
-        
-        @param target the start function of the target thread (i.e. the
-            user code)
-        @param args arguments to pass to target
-        @param kwargs keyword arguments to pass to target
-        @param mainThread True, if we are attaching to the already
-              started mainthread of the app
-        @return identifier of the created thread
-        """
-        try:
-            self.lockClient()
-            newThread = DebugThread(self, target, args, kwargs, mainThread)
-            ident = -1
-            if mainThread:
-                ident = _thread.get_ident()
-                self.mainThread = newThread
-                if self.debugging:
-                    sys.setprofile(newThread.profile)
-            else:
-                ident = _original_start_thread(newThread.bootstrap, ())
-                if self.mainThread is not None:
-                    self.tracePython = self.mainThread.tracePython
-            newThread.set_ident(ident)
-            self.threads[newThread.get_ident()] = newThread
-        finally:
-            self.unlockClient()
-        return ident
-    
-    def threadTerminated(self, dbgThread):
-        """
-        Public method called when a DebugThread has exited.
-        
-        @param dbgThread the DebugThread that has exited
-        """
-        self.lockClient()
-        try:
-            del self.threads[dbgThread.get_ident()]
-        except KeyError:
-            pass
-        finally:
-            self.unlockClient()
-    
-    def lockClient(self, blocking=True):
-        """
-        Public method to acquire the lock for this client.
-        
-        @param blocking flag to indicating a blocking lock
-        @type bool
-        @return flag indicating successful locking
-        @rtype bool
-        """
-        if blocking:
-            self.clientLock.acquire()
-        else:
-            return self.clientLock.acquire(blocking)
-    
-    def unlockClient(self):
-        """
-        Public method to release the lock for this client.
-        """
-        try:
-            self.clientLock.release()
-        except AssertionError:
-            pass
-    
-    def setCurrentThread(self, id):
-        """
-        Public method to set the current thread.
-
-        @param id the id the current thread should be set to.
-        """
-        try:
-            self.lockClient()
-            if id is None or id not in self.threads:
-                self.currentThread = None
-            else:
-                self.currentThread = self.threads[id]
-        finally:
-            self.unlockClient()
-    
-    def eventLoop(self, disablePolling=False):
-        """
-        Public method implementing our event loop.
-        
-        @param disablePolling flag indicating to enter an event loop with
-            polling disabled (boolean)
-        """
-        # make sure we set the current thread appropriately
-        threadid = _thread.get_ident()
-        self.setCurrentThread(threadid)
-        
-        DebugClientBase.DebugClientBase.eventLoop(self, disablePolling)
-        
-        self.setCurrentThread(None)
-
-    def set_quit(self):
-        """
-        Public method to do a 'set quit' on all threads.
-        """
-        try:
-            locked = self.lockClient(False)
-            try:
-                for key in self.threads:
-                    self.threads[key].set_quit()
-            except Exception:
-                pass
-        finally:
-            if locked:
-                self.unlockClient()
-
-# We are normally called by the debugger to execute directly.
-
-if __name__ == '__main__':
-    debugClient = DebugClientThreads()
-    debugClient.main()
-
-#
-# eflag: noqa = M702, E402
--- a/DebugClients/Python/DebugThread.py	Tue Oct 04 22:38:29 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,132 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
-#
-
-"""
-Module implementing the debug thread.
-"""
-
-import bdb
-import sys
-
-from DebugBase import DebugBase
-
-
-class DebugThread(DebugBase):
-    """
-    Class implementing a debug thread.
-
-    It represents a thread in the python interpreter that we are tracing.
-    
-    Provides simple wrapper methods around bdb for the 'owning' client to
-    call to step etc.
-    """
-    def __init__(self, dbgClient, targ=None, args=None, kwargs=None,
-                 mainThread=False):
-        """
-        Constructor
-        
-        @param dbgClient the owning client
-        @param targ the target method in the run thread
-        @param args  arguments to be passed to the thread
-        @param kwargs arguments to be passed to the thread
-        @param mainThread False if this thread is not the main script's thread
-        """
-        DebugBase.__init__(self, dbgClient)
-        
-        self._target = targ
-        self._args = args
-        self._kwargs = kwargs
-        self._mainThread = mainThread
-        # thread running tracks execution state of client code
-        # it will always be False for main thread as that is tracked
-        # by DebugClientThreads and Bdb...
-        self._threadRunning = False
-        
-        self.__ident = None  # id of this thread.
-        self.__name = ""
-        self.tracePython = False
-    
-    def set_ident(self, id):
-        """
-        Public method to set the id for this thread.
-        
-        @param id id for this thread (int)
-        """
-        self.__ident = id
-    
-    def get_ident(self):
-        """
-        Public method to return the id of this thread.
-        
-        @return the id of this thread (int)
-        """
-        return self.__ident
-    
-    def get_name(self):
-        """
-        Public method to return the name of this thread.
-        
-        @return name of this thread (string)
-        """
-        return self.__name
-    
-    def traceThread(self):
-        """
-        Public method to setup tracing for this thread.
-        """
-        self.set_trace()
-        if not self._mainThread:
-            self.set_continue(False)
-    
-    def bootstrap(self):
-        """
-        Public method to bootstrap the thread.
-        
-        It wraps the call to the user function to enable tracing
-        before hand.
-        """
-        try:
-            self._threadRunning = True
-            self.traceThread()
-            self._target(*self._args, **self._kwargs)
-        except bdb.BdbQuit:
-            pass
-        finally:
-            self._threadRunning = False
-            self.quitting = True
-            self._dbgClient.threadTerminated(self)
-            sys.settrace(None)
-            sys.setprofile(None)
-    
-    def trace_dispatch(self, frame, event, arg):
-        """
-        Public method wrapping the trace_dispatch of bdb.py.
-        
-        It wraps the call to dispatch tracing into
-        bdb to make sure we have locked the client to prevent multiple
-        threads from entering the client event loop.
-        
-        @param frame The current stack frame.
-        @param event The trace event (string)
-        @param arg The arguments
-        @return local trace function
-        """
-        try:
-            self._dbgClient.lockClient()
-            # if this thread came out of a lock, and we are quitting
-            # and we are still running, then get rid of tracing for this thread
-            if self.quitting and self._threadRunning:
-                sys.settrace(None)
-                sys.setprofile(None)
-            import threading
-            self.__name = threading.current_thread().name
-            retval = DebugBase.trace_dispatch(self, frame, event, arg)
-        finally:
-            self._dbgClient.unlockClient()
-        
-        return retval
-
-#
-# eflag: noqa = M702
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/DebugClients/Python/ThreadExtension.py	Thu Oct 06 22:28:12 2016 +0200
@@ -0,0 +1,182 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2014 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing an import hook patching thread modules to get debugged too.
+"""
+
+import os.path
+import sys
+
+if sys.version_info[0] == 2:
+    import thread as _thread
+else:
+    import _thread
+
+import threading
+
+from DebugBase import DebugBase
+
+
+class ThreadExtension(object):
+    """
+    Class implementing the thread support for the debugger.
+    
+    Provides methods for intercepting thread creation, retriving the running
+    threads and their name and state.
+    """
+    def __init__(self):
+        """
+        Constructor
+        """
+        self.threadNumber = 1
+        self._original_start_new_thread = None
+        
+        self.clientLock = threading.RLock()
+        
+        # dictionary of all threads running {id: DebugBase}
+        self.threads = {}
+
+        # the "current" thread, basically for variables view
+        self.currentThread = self
+        
+        # special objects representing the main scripts thread and frame
+        self.mainThread = self
+        
+        if sys.version_info[0] == 2:
+            self.threadModName = 'thread'
+        else:
+            self.threadModName = '_thread'
+        
+        # reset already imported thread module to apply hooks at next import
+        del sys.modules[self.threadModName]
+        del sys.modules['threading']
+        
+        # provide a hook to perform a hard breakpoint
+        # Use it like this:
+        # if hasattr(sys, 'breakpoint): sys.breakpoint()
+        sys.breakpoint = self.set_trace
+
+    def attachThread(self, target=None, args=None, kwargs=None,
+                     mainThread=False):
+        """
+        Public method to setup a thread for DebugClient to debug.
+        
+        If mainThread is True, then we are attaching to the already
+        started mainthread of the app and the rest of the args are ignored.
+        
+        @param target the start function of the target thread (i.e. the
+            user code)
+        @param args arguments to pass to target
+        @param kwargs keyword arguments to pass to target
+        @param mainThread True, if we are attaching to the already
+              started mainthread of the app
+        @return identifier of the created thread
+        """
+        return
+        try:
+            self.lockClient()
+            newThread = DebugThread(self, target, args, kwargs, mainThread)
+            ident = -1
+            if mainThread:
+                ident = _thread.get_ident()
+                self.mainThread = newThread
+                if self.debugging:
+                    sys.setprofile(newThread.profile)
+            else:
+                ident = _original_start_thread(newThread.bootstrap, ())
+                if self.mainThread is not None:
+                    self.tracePython = self.mainThread.tracePython
+            newThread.set_ident(ident)
+            self.threads[newThread.get_ident()] = newThread
+        finally:
+            self.unlockClient()
+        return ident
+    
+    def threadTerminated(self, dbgThread):
+        """
+        Public method called when a DebugThread has exited.
+        
+        @param dbgThread the DebugThread that has exited
+        """
+        self.lockClient()
+        try:
+            del self.threads[dbgThread.get_ident()]
+        except KeyError:
+            pass
+        finally:
+            self.unlockClient()
+    
+    def lockClient(self, blocking=True):
+        """
+        Public method to acquire the lock for this client.
+        
+        @param blocking flag to indicating a blocking lock
+        @type bool
+        @return flag indicating successful locking
+        @rtype bool
+        """
+        if blocking:
+            self.clientLock.acquire()
+        else:
+            return self.clientLock.acquire(blocking)
+    
+    def unlockClient(self):
+        """
+        Public method to release the lock for this client.
+        """
+        try:
+            self.clientLock.release()
+        except AssertionError:
+            pass
+    
+    def setCurrentThread(self, id):
+        """
+        Public method to set the current thread.
+
+        @param id the id the current thread should be set to.
+        """
+        try:
+            self.lockClient()
+            if id is None or id not in self.threads:
+                self.currentThread = None
+            else:
+                self.currentThread = self.threads[id]
+        finally:
+            self.unlockClient()
+    
+    def dumpThreadList(self):
+        """
+        Public method to send the list of threads.
+        """
+        threadList = []
+        if self.threads and self.currentThread:
+            # indication for the threaded debugger
+            currentId = self.currentThread.get_ident()
+            for t in self.threads.values():
+                d = {}
+                d["id"] = t.get_ident()
+                d["name"] = t.get_name()
+                d["broken"] = t.isBroken
+                threadList.append(d)
+        else:
+            currentId = -1
+            d = {}
+            d["id"] = -1
+            d["name"] = "MainThread"
+            if hasattr(self, "isBroken"):
+                d["broken"] = self.isBroken
+            else:
+                d["broken"] = False
+            threadList.append(d)
+        
+        self.sendJsonCommand("ResponseThreadList", {
+            "currentID": currentId,
+            "threadList": threadList,
+        })
+
+
+#
+# eflag: noqa = M702
--- a/eric6.e4p	Tue Oct 04 22:38:29 2016 +0200
+++ b/eric6.e4p	Thu Oct 06 22:28:12 2016 +0200
@@ -33,13 +33,12 @@
     <Source>DebugClients/Python/DebugClient.py</Source>
     <Source>DebugClients/Python/DebugClientBase.py</Source>
     <Source>DebugClients/Python/DebugClientCapabilities.py</Source>
-    <Source>DebugClients/Python/DebugClientThreads.py</Source>
     <Source>DebugClients/Python/DebugConfig.py</Source>
-    <Source>DebugClients/Python/DebugThread.py</Source>
     <Source>DebugClients/Python/DebugUtilities.py</Source>
     <Source>DebugClients/Python/DebugVariables.py</Source>
     <Source>DebugClients/Python/FlexCompleter.py</Source>
     <Source>DebugClients/Python/PyProfile.py</Source>
+    <Source>DebugClients/Python/ThreadExtension.py</Source>
     <Source>DebugClients/Python/__init__.py</Source>
     <Source>DebugClients/Python/coverage/__init__.py</Source>
     <Source>DebugClients/Python/coverage/__main__.py</Source>

eric ide

mercurial