Skip to content
Browse files

Break up a circular dependency on shutdown

  • Loading branch information...
1 parent 11c7b1d commit 7f4c12b33565c4d0802c4385bb2bbaa6c5735196 @mitsuhiko mitsuhiko committed Nov 5, 2011
Showing with 93 additions and 0 deletions.
  1. +6 −0 CHANGES
  2. +4 −0 flask/ctx.py
  3. +83 −0 flask/testsuite/regression.py
View
6 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
-------------
View
4 flask/ctx.py
@@ -150,6 +150,10 @@ def pop(self):
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
View
83 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, '<h1>42</h1>')
+
+ # 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

0 comments on commit 7f4c12b

Please sign in to comment.
Something went wrong with that request. Please try again.