--- a/eric6/DebugClients/Python/ThreadExtension.py Sat Feb 08 17:01:47 2020 +0100 +++ b/eric6/DebugClients/Python/ThreadExtension.py Sat Feb 08 17:02:40 2020 +0100 @@ -9,7 +9,6 @@ import os import sys -import importlib if sys.version_info[0] == 2: import thread as _thread @@ -35,11 +34,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() @@ -53,17 +48,6 @@ # 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'] - - sys.meta_path.insert(0, self) def attachThread(self, target=None, args=None, kwargs=None, mainThread=False): @@ -249,192 +233,180 @@ self.threads = {id_: thrd for id_, thrd in self.threads.items() if id_ in frames} - def find_module(self, fullname, path=None): + ####################################################################### + ## Methods below deal with patching various modules to support + ## debugging of threads. + ####################################################################### + + def patchPyThread(self, module): """ - 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 - """ - 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 [self.threadModName, 'PyQt4.QtCore', 'PyQt5.QtCore', - 'PySide.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 + """ + # 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 """ - module = importlib.import_module(fullname) - sys.modules[fullname] = module - if (fullname == self.threadModName 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 + # _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. - # _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 + @param run the run method of threading.Thread + @type method pointer + """ + 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. + """ + 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): + Constructor """ - Wrapper class for threading.Thread. - """ - 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) - - module.Thread = ThreadWrapper + # 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 + if sys.version_info[0] == 2: + timer = module._Timer + else: + timer = module.Timer - # Special handling of threading.(_)Timer - if sys.version_info[0] == 2: - timer = module._Timer - else: - 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) - - if sys.version_info[0] == 2: - module._Timer = TimerWrapper - else: - module.Timer = TimerWrapper + super(TimerWrapper, self).__init__( + interval, function, *args, **kwargs) + + if sys.version_info[0] == 2: + module._Timer = TimerWrapper + else: + module.Timer = TimerWrapper + + # Special handling of threading._DummyThread + class DummyThreadWrapper(module._DummyThread, ThreadWrapper): + """ + Wrapper class for threading._DummyThread. + """ + def __init__(self, *args, **kwargs): + """ + Constructor + """ + super(DummyThreadWrapper, self).__init__(*args, **kwargs) + + module._DummyThread = DummyThreadWrapper + + def patchQThread(self, module): + """ + Public method to patch the QtCore module's QThread. - # Special handling of threading._DummyThread - class DummyThreadWrapper(module._DummyThread, ThreadWrapper): - """ - Wrapper class for threading._DummyThread. - """ - def __init__(self, *args, **kwargs): - """ - Constructor - """ - super(DummyThreadWrapper, self).__init__(*args, **kwargs) + @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): + """ + Bootstrap for QThread, which reports exceptions correctly. - module._DummyThread = DummyThreadWrapper + @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 - # Add hook for *.QThread - elif (fullname in ['PyQt4.QtCore', 'PyQt5.QtCore', - 'PySide.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 - - def _bootstrapQThread(self, run): + newThread.id = ident + newThread.name = name + + _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) + + class QThreadWrapper(module.QThread): + """ + Wrapper class for *.QThread. + """ + def __init__(self, *args, **kwargs): """ - Bootstrap for QThread, which reports exceptions correctly. + 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)) - @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 - - # 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): - """ - Wrapper class for *.QThread. - """ - 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) - - module.QThread = QThreadWrapper - - self.enableImportHooks = True - return module + module.QThread = QThreadWrapper # # eflag: noqa = M702