1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
|
# Copyright (c) 2009 Denis Bilenko. See LICENSE for details.
"""
Low-level utilities.
"""
from __future__ import absolute_import, print_function, division
import functools
import gc
import pprint
import sys
import traceback
from greenlet import getcurrent
from greenlet import greenlet as RawGreenlet
from gevent._compat import PYPY
from gevent._compat import thread_mod_name
from gevent._util import _NONE
__all__ = [
'format_run_info',
'print_run_info',
'GreenletTree',
'wrap_errors',
'assert_switches',
]
# PyPy is very slow at formatting stacks
# for some reason.
_STACK_LIMIT = 20 if PYPY else None
def _noop():
return None
def _ready():
return False
class wrap_errors(object):
"""
Helper to make function return an exception, rather than raise it.
Because every exception that is unhandled by greenlet will be logged,
it is desirable to prevent non-error exceptions from leaving a greenlet.
This can done with a simple ``try/except`` construct::
def wrapped_func(*args, **kwargs):
try:
return func(*args, **kwargs)
except (TypeError, ValueError, AttributeError) as ex:
return ex
This class provides a shortcut to write that in one line::
wrapped_func = wrap_errors((TypeError, ValueError, AttributeError), func)
It also preserves ``__str__`` and ``__repr__`` of the original function.
"""
# QQQ could also support using wrap_errors as a decorator
def __init__(self, errors, func):
"""
Calling this makes a new function from *func*, such that it catches *errors* (an
:exc:`BaseException` subclass, or a tuple of :exc:`BaseException` subclasses) and
return it as a value.
"""
self.__errors = errors
self.__func = func
# Set __doc__, __wrapped__, etc, especially useful on Python 3.
functools.update_wrapper(self, func)
def __call__(self, *args, **kwargs):
func = self.__func
try:
return func(*args, **kwargs)
except self.__errors as ex:
return ex
def __str__(self):
return str(self.__func)
def __repr__(self):
return repr(self.__func)
def __getattr__(self, name):
return getattr(self.__func, name)
def print_run_info(thread_stacks=True, greenlet_stacks=True, limit=_NONE, file=None):
"""
Call `format_run_info` and print the results to *file*.
If *file* is not given, `sys.stderr` will be used.
.. versionadded:: 1.3b1
"""
lines = format_run_info(thread_stacks=thread_stacks,
greenlet_stacks=greenlet_stacks,
limit=limit)
file = sys.stderr if file is None else file
for l in lines:
print(l, file=file)
def format_run_info(thread_stacks=True,
greenlet_stacks=True,
limit=_NONE,
current_thread_ident=None):
"""
format_run_info(thread_stacks=True, greenlet_stacks=True, limit=None) -> [str]
Request information about the running threads of the current process.
This is a debugging utility. Its output has no guarantees other than being
intended for human consumption.
:keyword bool thread_stacks: If true, then include the stacks for
running threads.
:keyword bool greenlet_stacks: If true, then include the stacks for
running greenlets. (Spawning stacks will always be printed.)
Setting this to False can reduce the output volume considerably
without reducing the overall information if *thread_stacks* is true
and you can associate a greenlet to a thread (using ``thread_ident``
printed values).
:keyword int limit: If given, passed directly to `traceback.format_stack`.
If not given, this defaults to the whole stack under CPython, and a
smaller stack under PyPy.
:return: A sequence of text lines detailing the stacks of running
threads and greenlets. (One greenlet will duplicate one thread,
the current thread and greenlet. If there are multiple running threads,
the stack for the current greenlet may be incorrectly duplicated in multiple
greenlets.)
Extra information about
:class:`gevent.Greenlet` object will also be returned.
.. versionadded:: 1.3a1
.. versionchanged:: 1.3a2
Renamed from ``dump_stacks`` to reflect the fact that this
prints additional information about greenlets, including their
spawning stack, parent, locals, and any spawn tree locals.
.. versionchanged:: 1.3b1
Added the *thread_stacks*, *greenlet_stacks*, and *limit* params.
"""
if current_thread_ident is None:
from gevent import monkey
current_thread_ident = monkey.get_original(thread_mod_name, 'get_ident')()
lines = []
limit = _STACK_LIMIT if limit is _NONE else limit
_format_thread_info(lines, thread_stacks, limit, current_thread_ident)
_format_greenlet_info(lines, greenlet_stacks, limit)
return lines
def _format_thread_info(lines, thread_stacks, limit, current_thread_ident):
import threading
threads = {th.ident: th for th in threading.enumerate()}
lines.append('*' * 80)
lines.append('* Threads')
thread = None
frame = None
for thread_ident, frame in sys._current_frames().items():
lines.append("*" * 80)
thread = threads.get(thread_ident)
name = thread.name if thread else None
if getattr(thread, 'gevent_monitoring_thread', None):
name = repr(thread.gevent_monitoring_thread())
if current_thread_ident == thread_ident:
name = '%s) (CURRENT' % (name,)
lines.append('Thread 0x%x (%s)\n' % (thread_ident, name))
if thread_stacks:
lines.append(''.join(traceback.format_stack(frame, limit)))
else:
lines.append('\t...stack elided...')
# We may have captured our own frame, creating a reference
# cycle, so clear it out.
del thread
del frame
del lines
del threads
def _format_greenlet_info(lines, greenlet_stacks, limit):
# Use the gc module to inspect all objects to find the greenlets
# since there isn't a global registry
lines.append('*' * 80)
lines.append('* Greenlets')
lines.append('*' * 80)
for tree in GreenletTree.forest():
lines.extend(tree.format_lines(details={
'running_stacks': greenlet_stacks,
'running_stack_limit': limit,
}))
del lines
dump_stacks = format_run_info
def _line(f):
@functools.wraps(f)
def w(self, *args, **kwargs):
r = f(self, *args, **kwargs)
self.lines.append(r)
return w
class _TreeFormatter(object):
UP_AND_RIGHT = '+'
HORIZONTAL = '-'
VERTICAL = '|'
VERTICAL_AND_RIGHT = '+'
DATA = ':'
label_space = 1
horiz_width = 3
indent = 1
def __init__(self, details, depth=0):
self.lines = []
self.depth = depth
self.details = details
if not details:
self.child_data = lambda *args, **kwargs: None
def deeper(self):
return type(self)(self.details, self.depth + 1)
@_line
def node_label(self, text):
return text
@_line
def child_head(self, label, right=VERTICAL_AND_RIGHT):
return (
' ' * self.indent
+ right
+ self.HORIZONTAL * self.horiz_width
+ ' ' * self.label_space
+ label
)
def last_child_head(self, label):
return self.child_head(label, self.UP_AND_RIGHT)
@_line
def child_tail(self, line, vertical=VERTICAL):
return (
' ' * self.indent
+ vertical
+ ' ' * self.horiz_width
+ line
)
def last_child_tail(self, line):
return self.child_tail(line, vertical=' ' * len(self.VERTICAL))
@_line
def child_data(self, data, data_marker=DATA): # pylint:disable=method-hidden
return ((
' ' * self.indent
+ (data_marker if not self.depth else ' ')
+ ' ' * self.horiz_width
+ ' ' * self.label_space
+ data
),)
def last_child_data(self, data):
return self.child_data(data, ' ')
def child_multidata(self, data):
# Remove embedded newlines
for l in data.splitlines():
self.child_data(l)
class GreenletTree(object):
"""
Represents a tree of greenlets.
In gevent, the *parent* of a greenlet is usually the hub, so this
tree is primarily arganized along the *spawning_greenlet* dimension.
This object has a small str form showing this hierarchy. The `format`
method can output more details. The exact output is unspecified but is
intended to be human readable.
Use the `forest` method to get the root greenlet trees for
all threads, and the `current_tree` to get the root greenlet tree for
the current thread.
"""
#: The greenlet this tree represents.
greenlet = None
def __init__(self, greenlet):
self.greenlet = greenlet
self.child_trees = []
def add_child(self, tree):
if tree is self:
return
self.child_trees.append(tree)
@property
def root(self):
return self.greenlet.parent is None
def __getattr__(self, name):
return getattr(self.greenlet, name)
DEFAULT_DETAILS = {
'running_stacks': True,
'running_stack_limit': _STACK_LIMIT,
'spawning_stacks': True,
'locals': True,
}
def format_lines(self, details=True):
"""
Return a sequence of lines for the greenlet tree.
:keyword bool details: If true (the default),
then include more informative details in the output.
"""
if not isinstance(details, dict):
if not details:
details = {}
else:
details = self.DEFAULT_DETAILS.copy()
else:
params = details
details = self.DEFAULT_DETAILS.copy()
details.update(params)
tree = _TreeFormatter(details, depth=0)
lines = [l[0] if isinstance(l, tuple) else l
for l in self._render(tree)]
return lines
def format(self, details=True):
"""
Like `format_lines` but returns a string.
"""
lines = self.format_lines(details)
return '\n'.join(lines)
def __str__(self):
return self.format(False)
@staticmethod
def __render_tb(tree, label, frame, limit):
tree.child_data(label)
tb = ''.join(traceback.format_stack(frame, limit))
tree.child_multidata(tb)
@staticmethod
def __spawning_parent(greenlet):
return (getattr(greenlet, 'spawning_greenlet', None) or _noop)()
def __render_locals(self, tree):
# Defer the import to avoid cycles
from gevent.local import all_local_dicts_for_greenlet
gr_locals = all_local_dicts_for_greenlet(self.greenlet)
if gr_locals:
tree.child_data("Greenlet Locals:")
for (kind, idl), vals in gr_locals:
tree.child_data(" Local %s at %s" % (kind, hex(idl)))
tree.child_multidata(" " + pprint.pformat(vals))
def _render(self, tree):
label = repr(self.greenlet)
if not self.greenlet: # Not running or dead
# raw greenlets do not have ready
if getattr(self.greenlet, 'ready', _ready)():
label += '; finished'
if self.greenlet.value is not None:
label += ' with value ' + repr(self.greenlet.value)[:30]
elif getattr(self.greenlet, 'exception', None) is not None:
label += ' with exception ' + repr(self.greenlet.exception)
else:
label += '; not running'
tree.node_label(label)
tree.child_data('Parent: ' + repr(self.greenlet.parent))
if getattr(self.greenlet, 'gevent_monitoring_thread', None) is not None:
tree.child_data('Monitoring Thread:' + repr(self.greenlet.gevent_monitoring_thread()))
if self.greenlet and tree.details and tree.details['running_stacks']:
self.__render_tb(tree, 'Running:', self.greenlet.gr_frame,
tree.details['running_stack_limit'])
spawning_stack = getattr(self.greenlet, 'spawning_stack', None)
if spawning_stack and tree.details and tree.details['spawning_stacks']:
# We already placed a limit on the spawning stack when we captured it.
self.__render_tb(tree, 'Spawned at:', spawning_stack, None)
spawning_parent = self.__spawning_parent(self.greenlet)
tree_locals = getattr(self.greenlet, 'spawn_tree_locals', None)
if tree_locals and tree_locals is not getattr(spawning_parent, 'spawn_tree_locals', None):
tree.child_data('Spawn Tree Locals')
tree.child_multidata(pprint.pformat(tree_locals))
self.__render_locals(tree)
self.__render_children(tree)
return tree.lines
def __render_children(self, tree):
children = sorted(self.child_trees,
key=lambda c: (
# raw greenlets first
getattr(c, 'minimal_ident', -1),
# running greenlets first
getattr(c, 'ready', _ready)(),
id(c.parent)))
for n, child in enumerate(children):
child_tree = child._render(tree.deeper())
head = tree.child_head
tail = tree.child_tail
data = tree.child_data
if n == len(children) - 1:
# last child does not get the line drawn
head = tree.last_child_head
tail = tree.last_child_tail
data = tree.last_child_data
head(child_tree.pop(0))
for child_data in child_tree:
if isinstance(child_data, tuple):
data(child_data[0])
else:
tail(child_data)
return tree.lines
@staticmethod
def _root_greenlet(greenlet):
while greenlet.parent is not None and not getattr(greenlet, 'greenlet_tree_is_root', False):
greenlet = greenlet.parent
return greenlet
@classmethod
def _forest(cls):
main_greenlet = cls._root_greenlet(getcurrent())
trees = {}
roots = {}
current_tree = roots[main_greenlet] = trees[main_greenlet] = cls(main_greenlet)
for ob in gc.get_objects():
if not isinstance(ob, RawGreenlet):
continue
if getattr(ob, 'greenlet_tree_is_ignored', False):
continue
spawn_parent = cls.__spawning_parent(ob)
if spawn_parent is None:
root = cls._root_greenlet(ob)
try:
tree = roots[root]
except KeyError: # pragma: no cover
tree = GreenletTree(root)
roots[root] = trees[root] = tree
else:
try:
tree = trees[spawn_parent]
except KeyError: # pragma: no cover
tree = trees[spawn_parent] = cls(spawn_parent)
try:
child_tree = trees[ob]
except KeyError:
trees[ob] = child_tree = cls(ob)
tree.add_child(child_tree)
return roots, current_tree
@classmethod
def forest(cls):
"""
forest() -> sequence
Return a sequence of `GreenletTree`, one for each running
native thread.
"""
return list(cls._forest()[0].values())
@classmethod
def current_tree(cls):
"""
current_tree() -> GreenletTree
Returns the `GreenletTree` for the current thread.
"""
return cls._forest()[1]
class _FailedToSwitch(AssertionError):
pass
class assert_switches(object):
"""
A context manager for ensuring a block of code switches greenlets.
This performs a similar function as the :doc:`monitoring thread
</monitoring>`, but the scope is limited to the body of the with
statement. If the code within the body doesn't yield to the hub
(and doesn't raise an exception), then upon exiting the
context manager an :exc:`AssertionError` will be raised.
This is useful in unit tests and for debugging purposes.
:keyword float max_blocking_time: If given, the body is allowed
to block for up to this many fractional seconds before
an error is raised.
:keyword bool hub_only: If True, then *max_blocking_time* only
refers to the amount of time spent between switches into the
hub. If False, then it refers to the maximum time between
*any* switches. If *max_blocking_time* is not given, has no
effect.
Example::
# This will always raise an exception: nothing switched
with assert_switches():
pass
# This will never raise an exception; nothing switched,
# but it happened very fast
with assert_switches(max_blocking_time=1.0):
pass
.. versionadded:: 1.3
"""
hub = None
tracer = None
def __init__(self, max_blocking_time=None, hub_only=False):
self.max_blocking_time = max_blocking_time
self.hub_only = hub_only
def __enter__(self):
from gevent import get_hub
from gevent import _tracer
self.hub = hub = get_hub()
# TODO: We could optimize this to use the GreenletTracer
# installed by the monitoring thread, if there is one.
# As it is, we will chain trace calls back to it.
if not self.max_blocking_time:
self.tracer = _tracer.GreenletTracer()
elif self.hub_only:
self.tracer = _tracer.HubSwitchTracer(hub, self.max_blocking_time)
else:
self.tracer = _tracer.MaxSwitchTracer(hub, self.max_blocking_time)
self.tracer.monitor_current_greenlet_blocking()
return self
def __exit__(self, t, v, tb):
self.tracer.kill()
hub = self.hub; self.hub = None
tracer = self.tracer; self.tracer = None
# Only check if there was no exception raised, we
# don't want to hide anything
if t is not None:
return
did_block = tracer.did_block_hub(hub)
if did_block:
active_greenlet = did_block[1]
report_lines = tracer.did_block_hub_report(hub, active_greenlet, {})
raise _FailedToSwitch('\n'.join(report_lines))
|