247 |
231 |
248 # Clean up obsolet because terminated threads |
232 # Clean up obsolet because terminated threads |
249 self.threads = {id_: thrd for id_, thrd in self.threads.items() |
233 self.threads = {id_: thrd for id_, thrd in self.threads.items() |
250 if id_ in frames} |
234 if id_ in frames} |
251 |
235 |
252 def find_module(self, fullname, path=None): |
236 ####################################################################### |
253 """ |
237 ## Methods below deal with patching various modules to support |
254 Public method returning the module loader. |
238 ## debugging of threads. |
255 |
239 ####################################################################### |
256 @param fullname name of the module to be loaded |
240 |
257 @type str |
241 def patchPyThread(self, module): |
258 @param path path to resolve the module name |
242 """ |
259 @type str |
243 Public method to patch Python _thread (Python3) and thread (Python2) |
260 @return module loader object |
244 modules. |
261 @rtype object |
245 |
262 """ |
246 @param module reference to the imported module to be patched |
263 if fullname in sys.modules or not self.debugging: |
247 @type module |
264 return None |
248 """ |
265 |
249 # make thread hooks available to system |
266 if fullname in [self.threadModName, 'PyQt4.QtCore', 'PyQt5.QtCore', |
250 self._original_start_new_thread = module.start_new_thread |
267 'PySide.QtCore', 'PySide2.QtCore', 'greenlet', |
251 module.start_new_thread = self.attachThread |
268 'threading'] and self.enableImportHooks: |
252 |
269 # Disable hook to be able to import original module |
253 def patchGreenlet(self, module): |
270 self.enableImportHooks = False |
254 """ |
271 return self |
255 Public method to patch the 'greenlet' module. |
272 |
256 |
273 return None |
257 @param module reference to the imported module to be patched |
274 |
258 @type module |
275 def load_module(self, fullname): |
259 @return flag indicating that the module was processed |
276 """ |
260 @rtype bool |
277 Public method to load a module. |
261 """ |
278 |
262 # Check for greenlet.settrace |
279 @param fullname name of the module to be loaded |
263 if hasattr(module, 'settrace'): |
280 @type str |
264 DebugBase.pollTimerEnabled = False |
281 @return reference to the loaded module |
265 return True |
282 @rtype module |
266 return False |
283 """ |
267 |
284 module = importlib.import_module(fullname) |
268 def patchPyThreading(self, module): |
285 sys.modules[fullname] = module |
269 """ |
286 if (fullname == self.threadModName and |
270 Public method to patch the Python threading module. |
287 self._original_start_new_thread is None): |
271 |
288 # make thread hooks available to system |
272 @param module reference to the imported module to be patched |
289 self._original_start_new_thread = module.start_new_thread |
273 @type module |
290 module.start_new_thread = self.attachThread |
274 """ |
291 |
275 # _debugClient as a class attribute can't be accessed in following |
292 elif (fullname == 'greenlet' and self.greenlet is False): |
276 # class. Therefore we need a global variable. |
293 # Check for greenlet.settrace |
277 _debugClient = self |
294 if hasattr(module, 'settrace'): |
278 |
295 self.greenlet = True |
279 def _bootstrap(self, run): |
296 DebugBase.pollTimerEnabled = False |
280 """ |
297 |
281 Bootstrap for threading, which reports exceptions correctly. |
298 # Add hook for threading.run() |
282 |
299 elif (fullname == "threading" and self.threadingAttached is False): |
283 @param run the run method of threading.Thread |
300 self.threadingAttached = True |
284 @type method pointer |
301 |
285 """ |
302 # _debugClient as a class attribute can't be accessed in following |
286 newThread = DebugBase(_debugClient) |
303 # class. Therefore we need a global variable. |
287 _debugClient.threads[self.ident] = newThread |
304 _debugClient = self |
288 newThread.name = self.name |
305 |
289 # see DebugBase.bootstrap |
306 def _bootstrap(self, run): |
290 sys.settrace(newThread.trace_dispatch) |
307 """ |
291 try: |
308 Bootstrap for threading, which reports exceptions correctly. |
292 run() |
|
293 except Exception: |
|
294 excinfo = sys.exc_info() |
|
295 newThread.user_exception(excinfo, True) |
|
296 finally: |
|
297 sys.settrace(None) |
|
298 |
|
299 class ThreadWrapper(module.Thread): |
|
300 """ |
|
301 Wrapper class for threading.Thread. |
|
302 """ |
|
303 def __init__(self, *args, **kwargs): |
|
304 """ |
|
305 Constructor |
|
306 """ |
|
307 # Overwrite the provided run method with our own, to |
|
308 # intercept the thread creation by threading.Thread |
|
309 self.run = lambda s=self, run=self.run: _bootstrap(s, run) |
309 |
310 |
310 @param run the run method of threading.Thread |
311 super(ThreadWrapper, self).__init__(*args, **kwargs) |
311 @type method pointer |
312 |
312 """ |
313 module.Thread = ThreadWrapper |
313 newThread = DebugBase(_debugClient) |
314 |
314 _debugClient.threads[self.ident] = newThread |
315 # Special handling of threading.(_)Timer |
315 newThread.name = self.name |
316 if sys.version_info[0] == 2: |
316 # see DebugBase.bootstrap |
317 timer = module._Timer |
317 sys.settrace(newThread.trace_dispatch) |
318 else: |
318 try: |
319 timer = module.Timer |
319 run() |
320 |
320 except Exception: |
321 class TimerWrapper(timer, ThreadWrapper): |
321 excinfo = sys.exc_info() |
322 """ |
322 newThread.user_exception(excinfo, True) |
323 Wrapper class for threading.(_)Timer. |
323 finally: |
324 """ |
324 sys.settrace(None) |
325 def __init__(self, interval, function, *args, **kwargs): |
325 |
326 """ |
326 class ThreadWrapper(module.Thread): |
327 Constructor |
327 """ |
328 """ |
328 Wrapper class for threading.Thread. |
329 super(TimerWrapper, self).__init__( |
329 """ |
330 interval, function, *args, **kwargs) |
330 def __init__(self, *args, **kwargs): |
331 |
331 """ |
332 if sys.version_info[0] == 2: |
332 Constructor |
333 module._Timer = TimerWrapper |
333 """ |
334 else: |
334 # Overwrite the provided run method with our own, to |
335 module.Timer = TimerWrapper |
335 # intercept the thread creation by threading.Thread |
336 |
336 self.run = lambda s=self, run=self.run: _bootstrap(s, run) |
337 # Special handling of threading._DummyThread |
337 |
338 class DummyThreadWrapper(module._DummyThread, ThreadWrapper): |
338 super(ThreadWrapper, self).__init__(*args, **kwargs) |
339 """ |
339 |
340 Wrapper class for threading._DummyThread. |
340 module.Thread = ThreadWrapper |
341 """ |
341 |
342 def __init__(self, *args, **kwargs): |
342 # Special handling of threading.(_)Timer |
343 """ |
343 if sys.version_info[0] == 2: |
344 Constructor |
344 timer = module._Timer |
345 """ |
345 else: |
346 super(DummyThreadWrapper, self).__init__(*args, **kwargs) |
346 timer = module.Timer |
347 |
|
348 module._DummyThread = DummyThreadWrapper |
|
349 |
|
350 def patchQThread(self, module): |
|
351 """ |
|
352 Public method to patch the QtCore module's QThread. |
|
353 |
|
354 @param module reference to the imported module to be patched |
|
355 @type module |
|
356 """ |
|
357 # _debugClient as a class attribute can't be accessed in following |
|
358 # class. Therefore we need a global variable. |
|
359 _debugClient = self |
|
360 |
|
361 def _bootstrapQThread(self, run): |
|
362 """ |
|
363 Bootstrap for QThread, which reports exceptions correctly. |
|
364 |
|
365 @param run the run method of *.QThread |
|
366 @type method pointer |
|
367 """ |
|
368 global _qtThreadNumber |
|
369 |
|
370 newThread = DebugBase(_debugClient) |
|
371 ident = _thread.get_ident() |
|
372 name = 'QtThread-{0}'.format(_qtThreadNumber) |
|
373 |
|
374 _qtThreadNumber += 1 |
|
375 |
|
376 newThread.id = ident |
|
377 newThread.name = name |
|
378 |
|
379 _debugClient.threads[ident] = newThread |
|
380 |
|
381 # see DebugBase.bootstrap |
|
382 sys.settrace(newThread.trace_dispatch) |
|
383 try: |
|
384 run() |
|
385 except SystemExit: |
|
386 # *.QThreads doesn't like SystemExit |
|
387 pass |
|
388 except Exception: |
|
389 excinfo = sys.exc_info() |
|
390 newThread.user_exception(excinfo, True) |
|
391 finally: |
|
392 sys.settrace(None) |
|
393 |
|
394 class QThreadWrapper(module.QThread): |
|
395 """ |
|
396 Wrapper class for *.QThread. |
|
397 """ |
|
398 def __init__(self, *args, **kwargs): |
|
399 """ |
|
400 Constructor |
|
401 """ |
|
402 # Overwrite the provided run method with our own, to |
|
403 # intercept the thread creation by Qt |
|
404 self.run = lambda s=self, run=self.run: ( |
|
405 _bootstrapQThread(s, run)) |
347 |
406 |
348 class TimerWrapper(timer, ThreadWrapper): |
407 super(QThreadWrapper, self).__init__(*args, **kwargs) |
349 """ |
408 |
350 Wrapper class for threading.(_)Timer. |
409 module.QThread = QThreadWrapper |
351 """ |
|
352 def __init__(self, interval, function, *args, **kwargs): |
|
353 """ |
|
354 Constructor |
|
355 """ |
|
356 super(TimerWrapper, self).__init__( |
|
357 interval, function, *args, **kwargs) |
|
358 |
|
359 if sys.version_info[0] == 2: |
|
360 module._Timer = TimerWrapper |
|
361 else: |
|
362 module.Timer = TimerWrapper |
|
363 |
|
364 # Special handling of threading._DummyThread |
|
365 class DummyThreadWrapper(module._DummyThread, ThreadWrapper): |
|
366 """ |
|
367 Wrapper class for threading._DummyThread. |
|
368 """ |
|
369 def __init__(self, *args, **kwargs): |
|
370 """ |
|
371 Constructor |
|
372 """ |
|
373 super(DummyThreadWrapper, self).__init__(*args, **kwargs) |
|
374 |
|
375 module._DummyThread = DummyThreadWrapper |
|
376 |
|
377 # Add hook for *.QThread |
|
378 elif (fullname in ['PyQt4.QtCore', 'PyQt5.QtCore', |
|
379 'PySide.QtCore', 'PySide2.QtCore'] and |
|
380 self.qtThreadAttached is False): |
|
381 self.qtThreadAttached = True |
|
382 # _debugClient as a class attribute can't be accessed in following |
|
383 # class. Therefore we need a global variable. |
|
384 _debugClient = self |
|
385 |
|
386 def _bootstrapQThread(self, run): |
|
387 """ |
|
388 Bootstrap for QThread, which reports exceptions correctly. |
|
389 |
|
390 @param run the run method of *.QThread |
|
391 @type method pointer |
|
392 """ |
|
393 global _qtThreadNumber |
|
394 |
|
395 newThread = DebugBase(_debugClient) |
|
396 ident = _thread.get_ident() |
|
397 name = 'QtThread-{0}'.format(_qtThreadNumber) |
|
398 |
|
399 _qtThreadNumber += 1 |
|
400 |
|
401 newThread.id = ident |
|
402 newThread.name = name |
|
403 |
|
404 _debugClient.threads[ident] = newThread |
|
405 |
|
406 # see DebugBase.bootstrap |
|
407 sys.settrace(newThread.trace_dispatch) |
|
408 try: |
|
409 run() |
|
410 except SystemExit: |
|
411 # *.QThreads doesn't like SystemExit |
|
412 pass |
|
413 except Exception: |
|
414 excinfo = sys.exc_info() |
|
415 newThread.user_exception(excinfo, True) |
|
416 finally: |
|
417 sys.settrace(None) |
|
418 |
|
419 class QThreadWrapper(module.QThread): |
|
420 """ |
|
421 Wrapper class for *.QThread. |
|
422 """ |
|
423 def __init__(self, *args, **kwargs): |
|
424 """ |
|
425 Constructor |
|
426 """ |
|
427 # Overwrite the provided run method with our own, to |
|
428 # intercept the thread creation by Qt |
|
429 self.run = lambda s=self, run=self.run: ( |
|
430 _bootstrapQThread(s, run)) |
|
431 |
|
432 super(QThreadWrapper, self).__init__(*args, **kwargs) |
|
433 |
|
434 module.QThread = QThreadWrapper |
|
435 |
|
436 self.enableImportHooks = True |
|
437 return module |
|
438 |
410 |
439 # |
411 # |
440 # eflag: noqa = M702 |
412 # eflag: noqa = M702 |