From: Ben Darnell Date: Sun, 26 Mar 2017 18:43:23 +0000 (-0400) Subject: Check in test script for circular references X-Git-Tag: v4.5.0~14 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8a4fc8652c32dad0fbe627b08a3563404765a2a7;p=thirdparty%2Ftornado.git Check in test script for circular references From #1936 --- diff --git a/maint/circlerefs/circlerefs.py b/maint/circlerefs/circlerefs.py new file mode 100644 index 000000000..0fb59c6f9 --- /dev/null +++ b/maint/circlerefs/circlerefs.py @@ -0,0 +1,100 @@ +#!/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. +""" +from __future__ import print_function + +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') + + +application = web.Application([ + (r'/dummy/', DummyHandler), + (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/') + + # Now report on the results. + gc.collect() + resp = yield client.fetch('http://127.0.0.1:8888/collect/') + print(resp.body) + +if __name__ == "__main__": + ioloop.IOLoop.current().run_sync(main)