Thu, 06 Oct 2016 22:28:12 +0200
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>