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