eric6/DebugClients/Python/ThreadExtension.py

branch
multi_processing
changeset 7404
663f1c3d6f53
parent 7390
052ce4cf06c6
child 7412
0a995393d2ba
equal deleted inserted replaced
7403:7446a7eacfc3 7404:663f1c3d6f53
7 Module implementing an import hook patching thread modules to get debugged too. 7 Module implementing an import hook patching thread modules to get debugged too.
8 """ 8 """
9 9
10 import os 10 import os
11 import sys 11 import sys
12 import importlib
13 12
14 if sys.version_info[0] == 2: 13 if sys.version_info[0] == 2:
15 import thread as _thread 14 import thread as _thread
16 else: 15 else:
17 import _thread 16 import _thread
33 def __init__(self): 32 def __init__(self):
34 """ 33 """
35 Constructor 34 Constructor
36 """ 35 """
37 self.threadNumber = 1 36 self.threadNumber = 1
38 self.enableImportHooks = True
39 self._original_start_new_thread = None 37 self._original_start_new_thread = None
40 self.threadingAttached = False
41 self.qtThreadAttached = False
42 self.greenlet = False
43 38
44 self.clientLock = threading.RLock() 39 self.clientLock = threading.RLock()
45 40
46 # dictionary of all threads running {id: DebugBase} 41 # dictionary of all threads running {id: DebugBase}
47 self.threads = {_thread.get_ident(): self} 42 self.threads = {_thread.get_ident(): self}
51 # the thread we are at a breakpoint continuing at next command 46 # the thread we are at a breakpoint continuing at next command
52 self.currentThreadExec = self 47 self.currentThreadExec = self
53 48
54 # special objects representing the main scripts thread and frame 49 # special objects representing the main scripts thread and frame
55 self.mainThread = self 50 self.mainThread = self
56
57 if sys.version_info[0] == 2:
58 self.threadModName = 'thread'
59 else:
60 self.threadModName = '_thread'
61
62 # reset already imported thread module to apply hooks at next import
63 del sys.modules[self.threadModName]
64 del sys.modules['threading']
65
66 sys.meta_path.insert(0, self)
67 51
68 def attachThread(self, target=None, args=None, kwargs=None, 52 def attachThread(self, target=None, args=None, kwargs=None,
69 mainThread=False): 53 mainThread=False):
70 """ 54 """
71 Public method to setup a standard thread for DebugClient to debug. 55 Public method to setup a standard thread for DebugClient to debug.
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

eric ide

mercurial