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