blob: bd3b306dad63cdb5a7f2801979b6c7ee9091549e [file] [log] [blame]
Tor Norbyec667c1f2014-05-28 17:06:51 -07001from __future__ import nested_scopes
2from pydevd_constants import * # @UnusedWildImport
3import stackless # @UnresolvedImport
4from pydevd_tracing import SetTrace
5from pydevd_custom_frames import updateCustomFrame, removeCustomFrame, addCustomFrame
6from pydevd_comm import GetGlobalDebugger
7import weakref
8from pydevd_file_utils import GetFilenameAndBase
9from pydevd import DONT_TRACE
10
11
12# Used so that we don't loose the id (because we'll remove when it's not alive and would generate a new id for the
13# same tasklet).
14class TaskletToLastId:
15 '''
16 So, why not a WeakKeyDictionary?
17 The problem is that removals from the WeakKeyDictionary will create a new tasklet (as it adds a callback to
18 remove the key when it's garbage-collected), so, we can get into a recursion.
19 '''
20
21 def __init__(self):
22 self.tasklet_ref_to_last_id = {}
23 self._i = 0
24
25
26 def get(self, tasklet):
27 return self.tasklet_ref_to_last_id.get(weakref.ref(tasklet))
28
29
30 def __setitem__(self, tasklet, last_id):
31 self.tasklet_ref_to_last_id[weakref.ref(tasklet)] = last_id
32 self._i += 1
33 if self._i % 100 == 0: #Collect at each 100 additions to the dict (no need to rush).
34 for tasklet_ref in list(self.tasklet_ref_to_last_id.keys()):
35 if tasklet_ref() is None:
36 del self.tasklet_ref_to_last_id[tasklet_ref]
37
38
39_tasklet_to_last_id = TaskletToLastId()
40
41#=======================================================================================================================
42# _TaskletInfo
43#=======================================================================================================================
44class _TaskletInfo:
45
46 _last_id = 0
47
48 def __init__(self, tasklet_weakref, tasklet):
49 self.frame_id = None
50 self.tasklet_weakref = tasklet_weakref
51
52 last_id = _tasklet_to_last_id.get(tasklet)
53 if last_id is None:
54 _TaskletInfo._last_id += 1
55 last_id = _TaskletInfo._last_id
56 _tasklet_to_last_id[tasklet] = last_id
57
58 self._tasklet_id = last_id
59
60 self.update_name()
61
62 def update_name(self):
63 tasklet = self.tasklet_weakref()
64 if tasklet:
65 if tasklet.blocked:
66 state = 'blocked'
67 elif tasklet.paused:
68 state = 'paused'
69 elif tasklet.scheduled:
70 state = 'scheduled'
71 else:
72 state = '<UNEXPECTED>'
73
74 try:
75 name = tasklet.name
76 except AttributeError:
77 if tasklet.is_main:
78 name = 'MainTasklet'
79 else:
80 name = 'Tasklet-%s' % (self._tasklet_id,)
81
82 thread_id = tasklet.thread_id
83 if thread_id != -1:
84 for thread in threading.enumerate():
85 if thread.ident == thread_id:
86 if thread.name:
87 thread_name = "of %s" % (thread.name,)
88 else:
89 thread_name = "of Thread-%s" % (thread.name or str(thread_id),)
90 break
91 else:
92 # should not happen.
93 thread_name = "of Thread-%s" % (str(thread_id),)
94 thread = None
95 else:
96 # tasklet is no longer bound to a thread, because its thread ended
97 thread_name = "without thread"
98
99 tid = id(tasklet)
100 tasklet = None
101 else:
102 state = 'dead'
103 name = 'Tasklet-%s' % (self._tasklet_id,)
104 thread_name = ""
105 tid = '-'
106 self.tasklet_name = '%s %s %s (%s)' % (state, name, thread_name, tid)
107
108 if not hasattr(stackless.tasklet, "trace_function"):
109 # bug https://bitbucket.org/stackless-dev/stackless/issue/42
110 # is not fixed. Stackless releases before 2014
111 def update_name(self):
112 tasklet = self.tasklet_weakref()
113 if tasklet:
114 try:
115 name = tasklet.name
116 except AttributeError:
117 if tasklet.is_main:
118 name = 'MainTasklet'
119 else:
120 name = 'Tasklet-%s' % (self._tasklet_id,)
121
122 thread_id = tasklet.thread_id
123 for thread in threading.enumerate():
124 if thread.ident == thread_id:
125 if thread.name:
126 thread_name = "of %s" % (thread.name,)
127 else:
128 thread_name = "of Thread-%s" % (thread.name or str(thread_id),)
129 break
130 else:
131 # should not happen.
132 thread_name = "of Thread-%s" % (str(thread_id),)
133 thread = None
134
135 tid = id(tasklet)
136 tasklet = None
137 else:
138 name = 'Tasklet-%s' % (self._tasklet_id,)
139 thread_name = ""
140 tid = '-'
141 self.tasklet_name = '%s %s (%s)' % (name, thread_name, tid)
142
143_weak_tasklet_registered_to_info = {}
144
145#=======================================================================================================================
146# get_tasklet_info
147#=======================================================================================================================
148def get_tasklet_info(tasklet):
149 return register_tasklet_info(tasklet)
150
151
152#=======================================================================================================================
153# register_tasklet_info
154#=======================================================================================================================
155def register_tasklet_info(tasklet):
156 r = weakref.ref(tasklet)
157 info = _weak_tasklet_registered_to_info.get(r)
158 if info is None:
159 info = _weak_tasklet_registered_to_info[r] = _TaskletInfo(r, tasklet)
160
161 return info
162
163
164_application_set_schedule_callback = None
165
166#=======================================================================================================================
167# _schedule_callback
168#=======================================================================================================================
169def _schedule_callback(prev, next):
170 '''
171 Called when a context is stopped or a new context is made runnable.
172 '''
173 try:
174 if not prev and not next:
175 return
176
177 current_frame = sys._getframe()
178
179 if next:
180 register_tasklet_info(next)
181
182 # Ok, making next runnable: set the tracing facility in it.
183 debugger = GetGlobalDebugger()
184 if debugger is not None:
185 next.trace_function = debugger.trace_dispatch
186 frame = next.frame
187 if frame is current_frame:
188 frame = frame.f_back
189 if hasattr(frame, 'f_trace'): # Note: can be None (but hasattr should cover for that too).
190 frame.f_trace = debugger.trace_dispatch
191
192 debugger = None
193
194 if prev:
195 register_tasklet_info(prev)
196
197 try:
198 for tasklet_ref, tasklet_info in list(_weak_tasklet_registered_to_info.items()): # Make sure it's a copy!
199 tasklet = tasklet_ref()
200 if tasklet is None or not tasklet.alive:
201 # Garbage-collected already!
202 try:
203 del _weak_tasklet_registered_to_info[tasklet_ref]
204 except KeyError:
205 pass
206 if tasklet_info.frame_id is not None:
207 removeCustomFrame(tasklet_info.frame_id)
208 else:
209 is_running = stackless.get_thread_info(tasklet.thread_id)[1] is tasklet
210 if tasklet is prev or (tasklet is not next and not is_running):
211 # the tasklet won't run after this scheduler action:
212 # - the tasklet is the previous tasklet
213 # - it is not the next tasklet and it is not an already running tasklet
214 frame = tasklet.frame
215 if frame is current_frame:
216 frame = frame.f_back
217 if frame is not None:
218 _filename, base = GetFilenameAndBase(frame)
219 # print >>sys.stderr, "SchedCB: %r, %d, '%s', '%s'" % (tasklet, frame.f_lineno, _filename, base)
220 is_file_to_ignore = DictContains(DONT_TRACE, base)
221 if not is_file_to_ignore:
222 tasklet_info.update_name()
223 if tasklet_info.frame_id is None:
224 tasklet_info.frame_id = addCustomFrame(frame, tasklet_info.tasklet_name, tasklet.thread_id)
225 else:
226 updateCustomFrame(tasklet_info.frame_id, frame, tasklet.thread_id, name=tasklet_info.tasklet_name)
227
228 elif tasklet is next or is_running:
229 if tasklet_info.frame_id is not None:
230 # Remove info about stackless suspended when it starts to run.
231 removeCustomFrame(tasklet_info.frame_id)
232 tasklet_info.frame_id = None
233
234
235 finally:
236 tasklet = None
237 tasklet_info = None
238 frame = None
239
240 except:
241 import traceback;traceback.print_exc()
242
243 if _application_set_schedule_callback is not None:
244 return _application_set_schedule_callback(prev, next)
245
246if not hasattr(stackless.tasklet, "trace_function"):
247 # Older versions of Stackless, released before 2014
248 # This code does not work reliable! It is affected by several
249 # stackless bugs: Stackless issues #44, #42, #40
250 def _schedule_callback(prev, next):
251 '''
252 Called when a context is stopped or a new context is made runnable.
253 '''
254 try:
255 if not prev and not next:
256 return
257
258 if next:
259 register_tasklet_info(next)
260
261 # Ok, making next runnable: set the tracing facility in it.
262 debugger = GetGlobalDebugger()
263 if debugger is not None and next.frame:
264 if hasattr(next.frame, 'f_trace'):
265 next.frame.f_trace = debugger.trace_dispatch
266 debugger = None
267
268 if prev:
269 register_tasklet_info(prev)
270
271 try:
272 for tasklet_ref, tasklet_info in list(_weak_tasklet_registered_to_info.items()): # Make sure it's a copy!
273 tasklet = tasklet_ref()
274 if tasklet is None or not tasklet.alive:
275 # Garbage-collected already!
276 try:
277 del _weak_tasklet_registered_to_info[tasklet_ref]
278 except KeyError:
279 pass
280 if tasklet_info.frame_id is not None:
281 removeCustomFrame(tasklet_info.frame_id)
282 else:
283 if tasklet.paused or tasklet.blocked or tasklet.scheduled:
284 if tasklet.frame and tasklet.frame.f_back:
285 f_back = tasklet.frame.f_back
286 _filename, base = GetFilenameAndBase(f_back)
287 is_file_to_ignore = DictContains(DONT_TRACE, base)
288 if not is_file_to_ignore:
289 if tasklet_info.frame_id is None:
290 tasklet_info.frame_id = addCustomFrame(f_back, tasklet_info.tasklet_name, tasklet.thread_id)
291 else:
292 updateCustomFrame(tasklet_info.frame_id, f_back, tasklet.thread_id)
293
294 elif tasklet.is_current:
295 if tasklet_info.frame_id is not None:
296 # Remove info about stackless suspended when it starts to run.
297 removeCustomFrame(tasklet_info.frame_id)
298 tasklet_info.frame_id = None
299
300 finally:
301 tasklet = None
302 tasklet_info = None
303 f_back = None
304
305 except:
306 import traceback;traceback.print_exc()
307
308 if _application_set_schedule_callback is not None:
309 return _application_set_schedule_callback(prev, next)
310
311
312 _original_setup = stackless.tasklet.setup
313
314 #=======================================================================================================================
315 # setup
316 #=======================================================================================================================
317 def setup(self, *args, **kwargs):
318 '''
319 Called to run a new tasklet: rebind the creation so that we can trace it.
320 '''
321
322 f = self.tempval
323 def new_f(old_f, args, kwargs):
324
325 debugger = GetGlobalDebugger()
326 if debugger is not None:
327 SetTrace(debugger.trace_dispatch)
328
329 debugger = None
330
331 # Remove our own traces :)
332 self.tempval = old_f
333 register_tasklet_info(self)
334
335 # Hover old_f to see the stackless being created and *args and **kwargs to see its parameters.
336 return old_f(*args, **kwargs)
337
338 # This is the way to tell stackless that the function it should execute is our function, not the original one. Note:
339 # setting tempval is the same as calling bind(new_f), but it seems that there's no other way to get the currently
340 # bound function, so, keeping on using tempval instead of calling bind (which is actually the same thing in a better
341 # API).
342
343 self.tempval = new_f
344
345 return _original_setup(self, f, args, kwargs)
346
347 #=======================================================================================================================
348 # __call__
349 #=======================================================================================================================
350 def __call__(self, *args, **kwargs):
351 '''
352 Called to run a new tasklet: rebind the creation so that we can trace it.
353 '''
354
355 return setup(self, *args, **kwargs)
356
357
358 _original_run = stackless.run
359
360
361 #=======================================================================================================================
362 # run
363 #=======================================================================================================================
364 def run(*args, **kwargs):
365 debugger = GetGlobalDebugger()
366 if debugger is not None:
367 SetTrace(debugger.trace_dispatch)
368 debugger = None
369
370 return _original_run(*args, **kwargs)
371
372
373
374#=======================================================================================================================
375# patch_stackless
376#=======================================================================================================================
377def patch_stackless():
378 '''
379 This function should be called to patch the stackless module so that new tasklets are properly tracked in the
380 debugger.
381 '''
382 global _application_set_schedule_callback
383 _application_set_schedule_callback = stackless.set_schedule_callback(_schedule_callback)
384
385 def set_schedule_callback(callable):
386 global _application_set_schedule_callback
387 old = _application_set_schedule_callback
388 _application_set_schedule_callback = callable
389 return old
390
391 def get_schedule_callback(callable):
392 global _application_set_schedule_callback
393 return _application_set_schedule_callback
394
395 set_schedule_callback.__doc__ = stackless.set_schedule_callback.__doc__
396 if hasattr(stackless, "get_schedule_callback"):
397 get_schedule_callback.__doc__ = stackless.get_schedule_callback.__doc__
398 stackless.set_schedule_callback = set_schedule_callback
399 stackless.get_schedule_callback = get_schedule_callback
400
401 if not hasattr(stackless.tasklet, "trace_function"):
402 # Older versions of Stackless, released before 2014
403 __call__.__doc__ = stackless.tasklet.__call__.__doc__
404 stackless.tasklet.__call__ = __call__
405
406 setup.__doc__ = stackless.tasklet.setup.__doc__
407 stackless.tasklet.setup = setup
408
409 run.__doc__ = stackless.run.__doc__
410 stackless.run = run
411
412patch_stackless = call_only_once(patch_stackless)