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