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
|
#!/usr/bin/env python
"""Test script to find circular references.
Circular references are not leaks per se, because they will eventually
be GC'd. However, on CPython, they prevent the reference-counting fast
path from being used and instead rely on the slower full GC. This
increases memory footprint and CPU overhead, so we try to eliminate
circular references created by normal operation.
"""
import gc
import traceback
import types
from tornado import web, ioloop, gen, httpclient
def find_circular_references(garbage=None):
def inner(level):
for item in level:
item_id = id(item)
if item_id not in garbage_ids:
continue
if item_id in visited_ids:
continue
if item_id in stack_ids:
candidate = stack[stack.index(item):]
candidate.append(item)
found.append(candidate)
continue
stack.append(item)
stack_ids.add(item_id)
inner(gc.get_referents(item))
stack.pop()
stack_ids.remove(item_id)
visited_ids.add(item_id)
garbage = garbage or gc.garbage
found = []
stack = []
stack_ids = set()
garbage_ids = set(map(id, garbage))
visited_ids = set()
inner(garbage)
inner = None
return found
class CollectHandler(web.RequestHandler):
@gen.coroutine
def get(self):
self.write("Collected: {}\n".format(gc.collect()))
self.write("Garbage: {}\n".format(len(gc.garbage)))
for circular in find_circular_references():
print('\n==========\n Circular \n==========')
for item in circular:
print(' ', repr(item))
for item in circular:
if isinstance(item, types.FrameType):
print('\nLocals:', item.f_locals)
print('\nTraceback:', repr(item))
traceback.print_stack(item)
class DummyHandler(web.RequestHandler):
@gen.coroutine
def get(self):
self.write('ok\n')
class DummyAsyncHandler(web.RequestHandler):
@gen.coroutine
def get(self):
raise web.Finish('ok\n')
application = web.Application([
(r'/dummy/', DummyHandler),
(r'/dummyasync/', DummyAsyncHandler),
(r'/collect/', CollectHandler),
], debug=True)
@gen.coroutine
def main():
gc.disable()
gc.collect()
gc.set_debug(gc.DEBUG_STATS | gc.DEBUG_SAVEALL)
print('GC disabled')
print("Start on 8888")
application.listen(8888, '127.0.0.1')
# Do a little work. Alternately, could leave this script running and
# poke at it with a browser.
client = httpclient.AsyncHTTPClient()
yield client.fetch('http://127.0.0.1:8888/dummy/')
yield client.fetch('http://127.0.0.1:8888/dummyasync/', raise_error=False)
# Now report on the results.
resp = yield client.fetch('http://127.0.0.1:8888/collect/')
print(resp.body)
if __name__ == "__main__":
ioloop.IOLoop.current().run_sync(main)
|