Skip to content

Commit 8482ce6

Browse files
committed
Improve application context popping
Exceptions during teardown handling will no longer leave application contexts lingering around. This fixes pallets#1767
1 parent 87787b1 commit 8482ce6

File tree

3 files changed

+66
-38
lines changed

3 files changed

+66
-38
lines changed

CHANGES

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ Version 0.11
7777
- ``send_from_directory`` now raises BadRequest if the filename is invalid on
7878
the server OS (pull request ``#1763``).
7979
- Added the ``JSONIFY_MIMETYPE`` configuration variable (pull request ``#1728``).
80+
- Exceptions during teardown handling will no longer leave bad application
81+
contexts lingering around.
8082

8183
Version 0.10.2
8284
--------------

flask/ctx.py

Lines changed: 42 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -181,12 +181,14 @@ def push(self):
181181

182182
def pop(self, exc=_sentinel):
183183
"""Pops the app context."""
184-
self._refcnt -= 1
185-
if self._refcnt <= 0:
186-
if exc is _sentinel:
187-
exc = sys.exc_info()[1]
188-
self.app.do_teardown_appcontext(exc)
189-
rv = _app_ctx_stack.pop()
184+
try:
185+
self._refcnt -= 1
186+
if self._refcnt <= 0:
187+
if exc is _sentinel:
188+
exc = sys.exc_info()[1]
189+
self.app.do_teardown_appcontext(exc)
190+
finally:
191+
rv = _app_ctx_stack.pop()
190192
assert rv is self, 'Popped wrong app context. (%r instead of %r)' \
191193
% (rv, self)
192194
appcontext_popped.send(self.app)
@@ -341,38 +343,40 @@ def pop(self, exc=_sentinel):
341343
"""
342344
app_ctx = self._implicit_app_ctx_stack.pop()
343345

344-
clear_request = False
345-
if not self._implicit_app_ctx_stack:
346-
self.preserved = False
347-
self._preserved_exc = None
348-
if exc is _sentinel:
349-
exc = sys.exc_info()[1]
350-
self.app.do_teardown_request(exc)
351-
352-
# If this interpreter supports clearing the exception information
353-
# we do that now. This will only go into effect on Python 2.x,
354-
# on 3.x it disappears automatically at the end of the exception
355-
# stack.
356-
if hasattr(sys, 'exc_clear'):
357-
sys.exc_clear()
358-
359-
request_close = getattr(self.request, 'close', None)
360-
if request_close is not None:
361-
request_close()
362-
clear_request = True
363-
364-
rv = _request_ctx_stack.pop()
365-
assert rv is self, 'Popped wrong request context. (%r instead of %r)' \
366-
% (rv, self)
367-
368-
# get rid of circular dependencies at the end of the request
369-
# so that we don't require the GC to be active.
370-
if clear_request:
371-
rv.request.environ['werkzeug.request'] = None
372-
373-
# Get rid of the app as well if necessary.
374-
if app_ctx is not None:
375-
app_ctx.pop(exc)
346+
try:
347+
clear_request = False
348+
if not self._implicit_app_ctx_stack:
349+
self.preserved = False
350+
self._preserved_exc = None
351+
if exc is _sentinel:
352+
exc = sys.exc_info()[1]
353+
self.app.do_teardown_request(exc)
354+
355+
# If this interpreter supports clearing the exception information
356+
# we do that now. This will only go into effect on Python 2.x,
357+
# on 3.x it disappears automatically at the end of the exception
358+
# stack.
359+
if hasattr(sys, 'exc_clear'):
360+
sys.exc_clear()
361+
362+
request_close = getattr(self.request, 'close', None)
363+
if request_close is not None:
364+
request_close()
365+
clear_request = True
366+
finally:
367+
rv = _request_ctx_stack.pop()
368+
369+
# get rid of circular dependencies at the end of the request
370+
# so that we don't require the GC to be active.
371+
if clear_request:
372+
rv.request.environ['werkzeug.request'] = None
373+
374+
# Get rid of the app as well if necessary.
375+
if app_ctx is not None:
376+
app_ctx.pop(exc)
377+
378+
assert rv is self, 'Popped wrong request context. ' \
379+
'(%r instead of %r)' % (rv, self)
376380

377381
def auto_pop(self, exc):
378382
if self.request.environ.get('flask._preserve_context') or \

tests/test_appctx.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,25 @@ def index():
146146
assert res.status_code == 200
147147
assert res.data == b''
148148
assert called == ['request', 'app']
149+
150+
151+
def test_clean_pop():
152+
called = []
153+
app = flask.Flask(__name__)
154+
155+
@app.teardown_request
156+
def teardown_req(error=None):
157+
1 / 0
158+
159+
@app.teardown_appcontext
160+
def teardown_app(error=None):
161+
called.append('TEARDOWN')
162+
163+
try:
164+
with app.test_request_context():
165+
called.append(flask.current_app.name)
166+
except ZeroDivisionError:
167+
pass
168+
169+
assert called == ['test_appctx', 'TEARDOWN']
170+
assert not flask.current_app

0 commit comments

Comments
 (0)