src/eric7/DebugClients/Python/ThreadExtension.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9473
3f23dbf37dbe
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
20 20
21 21
22 class ThreadExtension: 22 class ThreadExtension:
23 """ 23 """
24 Class implementing the thread support for the debugger. 24 Class implementing the thread support for the debugger.
25 25
26 Provides methods for intercepting thread creation, retrieving the running 26 Provides methods for intercepting thread creation, retrieving the running
27 threads and their name and state. 27 threads and their name and state.
28 """ 28 """
29
29 def __init__(self): 30 def __init__(self):
30 """ 31 """
31 Constructor 32 Constructor
32 """ 33 """
33 self.threadNumber = 1 34 self.threadNumber = 1
34 self._original_start_new_thread = None 35 self._original_start_new_thread = None
35 36
36 self.clientLock = threading.RLock() 37 self.clientLock = threading.RLock()
37 38
38 # dictionary of all threads running {id: DebugBase} 39 # dictionary of all threads running {id: DebugBase}
39 self.threads = {_thread.get_ident(): self} 40 self.threads = {_thread.get_ident(): self}
40 41
41 # the "current" thread, basically for variables view 42 # the "current" thread, basically for variables view
42 self.currentThread = self 43 self.currentThread = self
43 # the thread we are at a breakpoint continuing at next command 44 # the thread we are at a breakpoint continuing at next command
44 self.currentThreadExec = self 45 self.currentThreadExec = self
45 46
46 # special objects representing the main scripts thread and frame 47 # special objects representing the main scripts thread and frame
47 self.mainThread = self 48 self.mainThread = self
48 49
49 def attachThread(self, target=None, args=None, kwargs=None, 50 def attachThread(self, target=None, args=None, kwargs=None, mainThread=False):
50 mainThread=False):
51 """ 51 """
52 Public method to setup a standard thread for DebugClient to debug. 52 Public method to setup a standard thread for DebugClient to debug.
53 53
54 If mainThread is True, then we are attaching to the already 54 If mainThread is True, then we are attaching to the already
55 started mainthread of the app and the rest of the args are ignored. 55 started mainthread of the app and the rest of the args are ignored.
56 56
57 @param target the start function of the target thread (i.e. the user 57 @param target the start function of the target thread (i.e. the user
58 code) 58 code)
59 @param args arguments to pass to target 59 @param args arguments to pass to target
60 @param kwargs keyword arguments to pass to target 60 @param kwargs keyword arguments to pass to target
61 @param mainThread True, if we are attaching to the already 61 @param mainThread True, if we are attaching to the already
62 started mainthread of the app 62 started mainthread of the app
63 @return identifier of the created thread 63 @return identifier of the created thread
64 """ 64 """
65 if kwargs is None: 65 if kwargs is None:
66 kwargs = {} 66 kwargs = {}
67 67
68 if mainThread: 68 if mainThread:
69 ident = _thread.get_ident() 69 ident = _thread.get_ident()
70 name = 'MainThread' 70 name = "MainThread"
71 newThread = self.mainThread 71 newThread = self.mainThread
72 newThread.isMainThread = True 72 newThread.isMainThread = True
73 if self.debugging: 73 if self.debugging:
74 sys.setprofile(newThread.profile) 74 sys.setprofile(newThread.profile)
75 75
76 else: 76 else:
77 newThread = DebugBase(self) 77 newThread = DebugBase(self)
78 ident = self._original_start_new_thread( 78 ident = self._original_start_new_thread(
79 newThread.bootstrap, (target, args, kwargs)) 79 newThread.bootstrap, (target, args, kwargs)
80 name = 'Thread-{0}'.format(self.threadNumber) 80 )
81 name = "Thread-{0}".format(self.threadNumber)
81 self.threadNumber += 1 82 self.threadNumber += 1
82 83
83 newThread.id = ident 84 newThread.id = ident
84 newThread.name = name 85 newThread.name = name
85 86
86 self.threads[ident] = newThread 87 self.threads[ident] = newThread
87 88
88 return ident 89 return ident
89 90
90 def threadTerminated(self, threadId): 91 def threadTerminated(self, threadId):
91 """ 92 """
92 Public method called when a DebugThread has exited. 93 Public method called when a DebugThread has exited.
93 94
94 @param threadId id of the DebugThread that has exited 95 @param threadId id of the DebugThread that has exited
95 @type int 96 @type int
96 """ 97 """
97 self.lockClient() 98 self.lockClient()
98 try: 99 try:
99 with contextlib.suppress(KeyError): 100 with contextlib.suppress(KeyError):
100 del self.threads[threadId] 101 del self.threads[threadId]
101 finally: 102 finally:
102 self.unlockClient() 103 self.unlockClient()
103 104
104 def lockClient(self, blocking=True): 105 def lockClient(self, blocking=True):
105 """ 106 """
106 Public method to acquire the lock for this client. 107 Public method to acquire the lock for this client.
107 108
108 @param blocking flag to indicating a blocking lock 109 @param blocking flag to indicating a blocking lock
109 @type bool 110 @type bool
110 @return flag indicating successful locking 111 @return flag indicating successful locking
111 @rtype bool 112 @rtype bool
112 """ 113 """
113 return self.clientLock.acquire(blocking) 114 return self.clientLock.acquire(blocking)
114 115
115 def unlockClient(self): 116 def unlockClient(self):
116 """ 117 """
117 Public method to release the lock for this client. 118 Public method to release the lock for this client.
118 """ 119 """
119 with contextlib.suppress(RuntimeError): 120 with contextlib.suppress(RuntimeError):
120 self.clientLock.release() 121 self.clientLock.release()
121 122
122 def setCurrentThread(self, threadId): 123 def setCurrentThread(self, threadId):
123 """ 124 """
124 Public method to set the current thread. 125 Public method to set the current thread.
125 126
126 @param threadId the id the current thread should be set to. 127 @param threadId the id the current thread should be set to.
132 self.currentThread = None 133 self.currentThread = None
133 else: 134 else:
134 self.currentThread = self.threads.get(threadId) 135 self.currentThread = self.threads.get(threadId)
135 finally: 136 finally:
136 self.unlockClient() 137 self.unlockClient()
137 138
138 def dumpThreadList(self): 139 def dumpThreadList(self):
139 """ 140 """
140 Public method to send the list of threads. 141 Public method to send the list of threads.
141 """ 142 """
142 self.updateThreadList() 143 self.updateThreadList()
143 144
144 threadList = [] 145 threadList = []
145 currentId = _thread.get_ident() 146 currentId = _thread.get_ident()
146 # update thread names set by user (threading.setName) 147 # update thread names set by user (threading.setName)
147 threadNames = {t.ident: t.getName() for t in threading.enumerate()} 148 threadNames = {t.ident: t.getName() for t in threading.enumerate()}
148 149
149 for threadId, thd in self.threads.items(): 150 for threadId, thd in self.threads.items():
150 d = {"id": threadId} 151 d = {"id": threadId}
151 try: 152 try:
152 d["name"] = threadNames.get(threadId, thd.name) 153 d["name"] = threadNames.get(threadId, thd.name)
153 d["broken"] = thd.isBroken 154 d["broken"] = thd.isBroken
154 d["except"] = thd.isException 155 d["except"] = thd.isException
155 except Exception: 156 except Exception:
156 d["name"] = 'UnknownThread' 157 d["name"] = "UnknownThread"
157 d["broken"] = False 158 d["broken"] = False
158 d["except"] = False 159 d["except"] = False
159 160
160 threadList.append(d) 161 threadList.append(d)
161 162
162 self.sendJsonCommand("ResponseThreadList", { 163 self.sendJsonCommand(
163 "currentID": currentId, 164 "ResponseThreadList",
164 "threadList": threadList, 165 {
165 }) 166 "currentID": currentId,
166 167 "threadList": threadList,
168 },
169 )
170
167 def getExecutedFrame(self, frame): 171 def getExecutedFrame(self, frame):
168 """ 172 """
169 Public method to return the currently executed frame. 173 Public method to return the currently executed frame.
170 174
171 @param frame the current frame 175 @param frame the current frame
172 @type frame object 176 @type frame object
173 @return the frame which is excecuted (without debugger frames) 177 @return the frame which is excecuted (without debugger frames)
174 @rtype frame object 178 @rtype frame object
175 """ 179 """
176 # to get the currently executed frame, skip all frames belonging to the 180 # to get the currently executed frame, skip all frames belonging to the
177 # debugger 181 # debugger
178 while frame is not None: 182 while frame is not None:
179 baseName = os.path.basename(frame.f_code.co_filename) 183 baseName = os.path.basename(frame.f_code.co_filename)
180 if not baseName.startswith( 184 if not baseName.startswith(
181 ('DebugClientBase.py', 'DebugBase.py', 'AsyncFile.py', 185 (
182 'ThreadExtension.py')): 186 "DebugClientBase.py",
187 "DebugBase.py",
188 "AsyncFile.py",
189 "ThreadExtension.py",
190 )
191 ):
183 break 192 break
184 frame = frame.f_back 193 frame = frame.f_back
185 194
186 return frame 195 return frame
187 196
188 def updateThreadList(self): 197 def updateThreadList(self):
189 """ 198 """
190 Public method to update the list of running threads. 199 Public method to update the list of running threads.
191 """ 200 """
192 frames = sys._current_frames() 201 frames = sys._current_frames()
193 for threadId, frame in frames.items(): 202 for threadId, frame in frames.items():
194 # skip our own timer thread 203 # skip our own timer thread
195 if frame.f_code.co_name == '__eventPollTimer': 204 if frame.f_code.co_name == "__eventPollTimer":
196 continue 205 continue
197 206
198 # Unknown thread 207 # Unknown thread
199 if threadId not in self.threads: 208 if threadId not in self.threads:
200 newThread = DebugBase(self) 209 newThread = DebugBase(self)
201 name = 'Thread-{0}'.format(self.threadNumber) 210 name = "Thread-{0}".format(self.threadNumber)
202 self.threadNumber += 1 211 self.threadNumber += 1
203 212
204 newThread.id = threadId 213 newThread.id = threadId
205 newThread.name = name 214 newThread.name = name
206 self.threads[threadId] = newThread 215 self.threads[threadId] = newThread
207 216
208 # adjust current frame 217 # adjust current frame
209 if "__pypy__" not in sys.builtin_module_names: 218 if "__pypy__" not in sys.builtin_module_names:
210 # Don't update with None 219 # Don't update with None
211 currentFrame = self.getExecutedFrame(frame) 220 currentFrame = self.getExecutedFrame(frame)
212 if (currentFrame is not None and 221 if (
213 self.threads[threadId].isBroken is False): 222 currentFrame is not None
223 and self.threads[threadId].isBroken is False
224 ):
214 self.threads[threadId].currentFrame = currentFrame 225 self.threads[threadId].currentFrame = currentFrame
215 226
216 # Clean up obsolet because terminated threads 227 # Clean up obsolet because terminated threads
217 self.threads = {id_: thrd for id_, thrd in self.threads.items() 228 self.threads = {
218 if id_ in frames} 229 id_: thrd for id_, thrd in self.threads.items() if id_ in frames
219 230 }
231
220 ####################################################################### 232 #######################################################################
221 ## Methods below deal with patching various modules to support 233 ## Methods below deal with patching various modules to support
222 ## debugging of threads. 234 ## debugging of threads.
223 ####################################################################### 235 #######################################################################
224 236
225 def patchPyThread(self, module): 237 def patchPyThread(self, module):
226 """ 238 """
227 Public method to patch Python _thread module. 239 Public method to patch Python _thread module.
228 240
229 @param module reference to the imported module to be patched 241 @param module reference to the imported module to be patched
230 @type module 242 @type module
231 """ 243 """
232 # make thread hooks available to system 244 # make thread hooks available to system
233 self._original_start_new_thread = module.start_new_thread 245 self._original_start_new_thread = module.start_new_thread
234 module.start_new_thread = self.attachThread 246 module.start_new_thread = self.attachThread
235 247
236 def patchGreenlet(self, module): 248 def patchGreenlet(self, module):
237 """ 249 """
238 Public method to patch the 'greenlet' module. 250 Public method to patch the 'greenlet' module.
239 251
240 @param module reference to the imported module to be patched 252 @param module reference to the imported module to be patched
241 @type module 253 @type module
242 @return flag indicating that the module was processed 254 @return flag indicating that the module was processed
243 @rtype bool 255 @rtype bool
244 """ 256 """
245 # Check for greenlet.settrace 257 # Check for greenlet.settrace
246 if hasattr(module, 'settrace'): 258 if hasattr(module, "settrace"):
247 DebugBase.pollTimerEnabled = False 259 DebugBase.pollTimerEnabled = False
248 return True 260 return True
249 return False 261 return False
250 262
251 def patchPyThreading(self, module): 263 def patchPyThreading(self, module):
252 """ 264 """
253 Public method to patch the Python threading module. 265 Public method to patch the Python threading module.
254 266
255 @param module reference to the imported module to be patched 267 @param module reference to the imported module to be patched
256 @type module 268 @type module
257 """ 269 """
258 # _debugClient as a class attribute can't be accessed in following 270 # _debugClient as a class attribute can't be accessed in following
259 # class. Therefore we need a global variable. 271 # class. Therefore we need a global variable.
260 _debugClient = self 272 _debugClient = self
261 273
262 def _bootstrap(self, run): 274 def _bootstrap(self, run):
263 """ 275 """
264 Bootstrap for threading, which reports exceptions correctly. 276 Bootstrap for threading, which reports exceptions correctly.
265 277
266 @param run the run method of threading.Thread 278 @param run the run method of threading.Thread
267 @type method pointer 279 @type method pointer
268 """ 280 """
269 newThread = DebugBase(_debugClient) 281 newThread = DebugBase(_debugClient)
270 newThread.name = self.name 282 newThread.name = self.name
271 283
272 _debugClient.threads[self.ident] = newThread 284 _debugClient.threads[self.ident] = newThread
273 _debugClient.dumpThreadList() 285 _debugClient.dumpThreadList()
274 286
275 # see DebugBase.bootstrap 287 # see DebugBase.bootstrap
276 sys.settrace(newThread.trace_dispatch) 288 sys.settrace(newThread.trace_dispatch)
277 try: 289 try:
278 run() 290 run()
279 except Exception: 291 except Exception:
280 excinfo = sys.exc_info() 292 excinfo = sys.exc_info()
281 newThread.user_exception(excinfo, True) 293 newThread.user_exception(excinfo, True)
282 finally: 294 finally:
283 sys.settrace(None) 295 sys.settrace(None)
284 _debugClient.dumpThreadList() 296 _debugClient.dumpThreadList()
285 297
286 class ThreadWrapper(module.Thread): 298 class ThreadWrapper(module.Thread):
287 """ 299 """
288 Wrapper class for threading.Thread. 300 Wrapper class for threading.Thread.
289 """ 301 """
302
290 def __init__(self, *args, **kwargs): 303 def __init__(self, *args, **kwargs):
291 """ 304 """
292 Constructor 305 Constructor
293 """ 306 """
294 # Overwrite the provided run method with our own, to 307 # Overwrite the provided run method with our own, to
295 # intercept the thread creation by threading.Thread 308 # intercept the thread creation by threading.Thread
296 self.run = lambda s=self, run=self.run: _bootstrap(s, run) 309 self.run = lambda s=self, run=self.run: _bootstrap(s, run)
297 310
298 super().__init__(*args, **kwargs) 311 super().__init__(*args, **kwargs)
299 312
300 module.Thread = ThreadWrapper 313 module.Thread = ThreadWrapper
301 314
302 # Special handling of threading.(_)Timer 315 # Special handling of threading.(_)Timer
303 timer = module.Timer 316 timer = module.Timer
304 317
305 class TimerWrapper(timer, ThreadWrapper): 318 class TimerWrapper(timer, ThreadWrapper):
306 """ 319 """
307 Wrapper class for threading.(_)Timer. 320 Wrapper class for threading.(_)Timer.
308 """ 321 """
322
309 def __init__(self, interval, function, *args, **kwargs): 323 def __init__(self, interval, function, *args, **kwargs):
310 """ 324 """
311 Constructor 325 Constructor
312 """ 326 """
313 super().__init__( 327 super().__init__(interval, function, *args, **kwargs)
314 interval, function, *args, **kwargs) 328
315
316 module.Timer = TimerWrapper 329 module.Timer = TimerWrapper
317 330
318 # Special handling of threading._DummyThread 331 # Special handling of threading._DummyThread
319 class DummyThreadWrapper(module._DummyThread, ThreadWrapper): 332 class DummyThreadWrapper(module._DummyThread, ThreadWrapper):
320 """ 333 """
321 Wrapper class for threading._DummyThread. 334 Wrapper class for threading._DummyThread.
322 """ 335 """
336
323 def __init__(self, *args, **kwargs): 337 def __init__(self, *args, **kwargs):
324 """ 338 """
325 Constructor 339 Constructor
326 """ 340 """
327 super().__init__(*args, **kwargs) 341 super().__init__(*args, **kwargs)
328 342
329 module._DummyThread = DummyThreadWrapper 343 module._DummyThread = DummyThreadWrapper
330 344
331 def patchQThread(self, module): 345 def patchQThread(self, module):
332 """ 346 """
333 Public method to patch the QtCore module's QThread. 347 Public method to patch the QtCore module's QThread.
334 348
335 @param module reference to the imported module to be patched 349 @param module reference to the imported module to be patched
336 @type module 350 @type module
337 """ 351 """
338 # _debugClient as a class attribute can't be accessed in following 352 # _debugClient as a class attribute can't be accessed in following
339 # class. Therefore we need a global variable. 353 # class. Therefore we need a global variable.
340 _debugClient = self 354 _debugClient = self
341 355
342 def _bootstrapQThread(self, run): 356 def _bootstrapQThread(self, run):
343 """ 357 """
344 Bootstrap for QThread, which reports exceptions correctly. 358 Bootstrap for QThread, which reports exceptions correctly.
345 359
346 @param run the run method of *.QThread 360 @param run the run method of *.QThread
347 @type method pointer 361 @type method pointer
348 """ 362 """
349 global _qtThreadNumber 363 global _qtThreadNumber
350 364
351 newThread = DebugBase(_debugClient) 365 newThread = DebugBase(_debugClient)
352 ident = _thread.get_ident() 366 ident = _thread.get_ident()
353 name = 'QtThread-{0}'.format(_qtThreadNumber) 367 name = "QtThread-{0}".format(_qtThreadNumber)
354 368
355 _qtThreadNumber += 1 369 _qtThreadNumber += 1
356 370
357 newThread.id = ident 371 newThread.id = ident
358 newThread.name = name 372 newThread.name = name
359 373
360 _debugClient.threads[ident] = newThread 374 _debugClient.threads[ident] = newThread
361 _debugClient.dumpThreadList() 375 _debugClient.dumpThreadList()
362 376
363 # see DebugBase.bootstrap 377 # see DebugBase.bootstrap
364 sys.settrace(newThread.trace_dispatch) 378 sys.settrace(newThread.trace_dispatch)
365 try: 379 try:
366 run() 380 run()
367 except SystemExit: 381 except SystemExit:
371 excinfo = sys.exc_info() 385 excinfo = sys.exc_info()
372 newThread.user_exception(excinfo, True) 386 newThread.user_exception(excinfo, True)
373 finally: 387 finally:
374 sys.settrace(None) 388 sys.settrace(None)
375 _debugClient.dumpThreadList() 389 _debugClient.dumpThreadList()
376 390
377 class QThreadWrapper(module.QThread): 391 class QThreadWrapper(module.QThread):
378 """ 392 """
379 Wrapper class for *.QThread. 393 Wrapper class for *.QThread.
380 """ 394 """
395
381 def __init__(self, *args, **kwargs): 396 def __init__(self, *args, **kwargs):
382 """ 397 """
383 Constructor 398 Constructor
384 """ 399 """
385 # Overwrite the provided run method with our own, to 400 # Overwrite the provided run method with our own, to
386 # intercept the thread creation by Qt 401 # intercept the thread creation by Qt
387 self.run = lambda s=self, run=self.run: ( 402 self.run = lambda s=self, run=self.run: (_bootstrapQThread(s, run))
388 _bootstrapQThread(s, run)) 403
389
390 super().__init__(*args, **kwargs) 404 super().__init__(*args, **kwargs)
391 405
392 class QRunnableWrapper(module.QRunnable): 406 class QRunnableWrapper(module.QRunnable):
393 """ 407 """
394 Wrapper class for *.QRunnable. 408 Wrapper class for *.QRunnable.
395 """ 409 """
410
396 def __init__(self, *args, **kwargs): 411 def __init__(self, *args, **kwargs):
397 """ 412 """
398 Constructor 413 Constructor
399 """ 414 """
400 # Overwrite the provided run method with our own, to 415 # Overwrite the provided run method with our own, to
401 # intercept the thread creation by Qt 416 # intercept the thread creation by Qt
402 self.run = lambda s=self, run=self.run: ( 417 self.run = lambda s=self, run=self.run: (_bootstrapQThread(s, run))
403 _bootstrapQThread(s, run)) 418
404
405 super().__init__(*args, **kwargs) 419 super().__init__(*args, **kwargs)
406 420
407 module.QThread = QThreadWrapper 421 module.QThread = QThreadWrapper
408 module.QRunnable = QRunnableWrapper 422 module.QRunnable = QRunnableWrapper

eric ide

mercurial