diff --git a/CHANGES b/CHANGES index ff6bdef2..e30979d8 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,12 @@ Relase date to be decided, codename to be chosen. URL rules specific to a given HTTP method. - Logger now only returns the debug log setting if it was not set explicitly. +- Unregister a circular dependency between the WSGI environment and + the request object when shutting down the request. This means that + environ ``werkzeug.request`` will be `None` after the response was + returned to the WSGI server but has the advantage that the garbage + collector is not needed on CPython to tear down the request unless + the user created circular dependencies themselves. Version 0.8.1 ------------- diff --git a/flask/ctx.py b/flask/ctx.py index 26781dbd..9a72d251 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -150,6 +150,10 @@ class RequestContext(object): assert rv is self, 'Popped wrong request context. (%r instead of %r)' \ % (rv, self) + # get rid of circular dependencies at the end of the request + # so that we don't require the GC to be active. + rv.request.environ['werkzeug.request'] = None + def __enter__(self): self.push() return self diff --git a/flask/testsuite/regression.py b/flask/testsuite/regression.py new file mode 100644 index 00000000..51a866a4 --- /dev/null +++ b/flask/testsuite/regression.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.regression + ~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Tests regressions. + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + +from __future__ import with_statement + +import gc +import sys +import flask +import threading +import unittest +from werkzeug.test import run_wsgi_app, create_environ +from flask.testsuite import FlaskTestCase + + +_gc_lock = threading.Lock() + + +class _NoLeakAsserter(object): + + def __init__(self, testcase): + self.testcase = testcase + + def __enter__(self): + gc.disable() + _gc_lock.acquire() + loc = flask._request_ctx_stack._local + + # Force Python to track this dictionary at all times. + # This is necessary since Python only starts tracking + # dicts if they contain mutable objects. It's a horrible, + # horrible hack but makes this kinda testable. + loc.__storage__['FOOO'] = [1, 2, 3] + + gc.collect() + self.old_objects = len(gc.get_objects()) + + def __exit__(self, exc_type, exc_value, tb): + if not hasattr(sys, 'getrefcount'): + gc.collect() + new_objects = len(gc.get_objects()) + if new_objects > self.old_objects: + self.testcase.fail('Example code leaked') + _gc_lock.release() + gc.enable() + + +class MemoryTestCase(FlaskTestCase): + + def assert_no_leak(self): + return _NoLeakAsserter(self) + + def test_memory_consumption(self): + app = flask.Flask(__name__) + @app.route('/') + def index(): + return flask.render_template('simple_template.html', whiskey=42) + + def fire(): + with app.test_client() as c: + rv = c.get('/') + self.assert_equal(rv.status_code, 200) + self.assert_equal(rv.data, '

42

') + + # Trigger caches + fire() + + with self.assert_no_leak(): + for x in xrange(10): + fire() + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(MemoryTestCase)) + return suite