Resource Cleanup and Teardown
Resource cleanup and teardown in this codebase are managed by the AppContext class, which ensures that application-level resources (like database connections or file handles) are properly closed when a context ends. This mechanism is designed to be robust, executing all registered cleanup tasks even if some of them encounter errors.
The AppContext Lifecycle
The AppContext (found in src/flask/ctx.py) manages the lifecycle of an application's state. It is typically used via a context manager, which handles the transition between "pushing" the context onto the stack and "popping" it off.
Push and Pop Mechanism
When a context is pushed, it becomes the active context for the current thread or task, making current_app and g available. The AppContext.push() method increments an internal _push_count to support nested contexts.
# src/flask/ctx.py
def push(self) -> None:
self._push_count += 1
if self._cv_token is not None:
return
self._cv_token = _cv_app.set(self)
appcontext_pushed.send(self.app, _async_wrapper=self.app.ensure_sync)
# ... session and routing setup ...
The pop() method is where the actual cleanup occurs. It only triggers the teardown process when the _push_count returns to zero, ensuring that nested contexts (common in testing or streaming) do not prematurely close resources.
Context Manager Usage
The most common way to manage this lifecycle is through the with statement, which calls push() on entry and pop() on exit.
with app.app_context():
# Resources are active here
do_something()
# pop() is called here, triggering teardown
Teardown Execution
When AppContext.pop() is executed, it triggers the teardown sequence. This involves calling functions registered via the @app.teardown_appcontext decorator.
Execution Order
Teardown functions are executed in reverse order of their registration. This allows dependencies to be cleaned up in the correct sequence (e.g., closing a database cursor before closing the connection). This logic is implemented in Flask.do_teardown_appcontext:
# src/flask/app.py
def do_teardown_appcontext(
self, ctx: AppContext, exc: BaseException | None = None
) -> None:
collect_errors = _CollectErrors()
for func in reversed(self.teardown_appcontext_funcs):
with collect_errors:
self.ensure_sync(func)(exc)
with collect_errors:
appcontext_tearing_down.send(
self, _async_wrapper=self.ensure_sync, app_context=ctx, exc=exc
)
collect_errors.raise_any("Errors during app context teardown")
Guaranteed Cleanup with _CollectErrors
A critical feature of the teardown process is the use of the _CollectErrors helper (defined in src/flask/helpers.py). This context manager ensures that an exception in one teardown function does not prevent subsequent teardown functions from running.
# src/flask/helpers.py
class _CollectErrors:
def __init__(self) -> None:
self.errors: list[BaseException] = []
def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
if exc_val is not None:
self.errors.append(exc_val)
return True # Silences the error during execution
def raise_any(self, message: str) -> None:
if self.errors:
if sys.version_info >= (3, 11):
raise BaseExceptionGroup(message, self.errors)
else:
raise self.errors[0]
In AppContext.pop(), multiple blocks are wrapped in _CollectErrors to guarantee that request teardown, request closing, and application context teardown all occur regardless of failures in previous steps.
Exception Handling in Teardown
Teardown functions receive an exc argument, which is the exception that caused the context to exit, or None if the context exited successfully.
- Successful Exit: If the block inside the
with app.app_context():finishes without error,pop(None)is called. - Unhandled Exception: If an exception occurs, the
__exit__method ofAppContextcaptures it and passes it topop(exc_value).
This allows teardown functions to perform conditional cleanup based on whether an error occurred, such as rolling back a database transaction instead of committing it.
@app.teardown_appcontext
def shutdown_session(exception=None):
if exception is None:
db_session.commit()
else:
db_session.rollback()
db_session.remove()
Propagation and Signals
After all teardown functions have run, the appcontext_popped signal is sent. If any errors were collected by _CollectErrors during the teardown process, they are raised at the very end of the pop() call, potentially as a BaseExceptionGroup on modern Python versions.