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: |