--- /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