Skip to main content

Context Teardown and Cleanup

When you open a database connection or a file handle during a request, you need a guaranteed way to close it, even if the request fails with an unhandled exception. If you don't, your application will eventually leak resources, leading to "Too many open files" or exhausted connection pools. Flask solves this by triggering a teardown process whenever an application context is removed.

Registering Teardown Functions

To ensure a resource is cleaned up, register a function using the @app.teardown_appcontext decorator. Flask calls these functions at the end of every request or CLI command.

from flask import Flask, g

app = Flask(__name__)

@app.teardown_appcontext
def close_db_connection(exception=None):
db = g.pop("db", None)
if db is not None:
db.close()

The function receives one argument: the exception that caused the context to end, or None if the request finished successfully.

The Teardown Lifecycle

The teardown process is managed by the AppContext.pop() method in src/flask/ctx.py. This method is called automatically when a with app.app_context(): block exits or when the WSGI server finishes processing a request.

When pop() is invoked, Flask executes cleanup in a specific sequence:

  1. Request Teardown: If the context contains request data, Flask first calls functions registered with @app.teardown_request via self.app.do_teardown_request(self, exc).
  2. Request Closing: The request object itself is closed via self._request.close().
  3. App Context Teardown: Flask calls functions registered with @app.teardown_appcontext via self.app.do_teardown_appcontext(self, exc).
  4. Signal Dispatch: The appcontext_popped signal is sent.

Reverse Execution Order

Teardown functions are executed in the reverse order of their registration. If you register function A and then function B, Flask will execute B then A. This ensures that if B depends on a resource set up by A, that resource is still available when B runs.

Internally, Flask.do_teardown_appcontext in src/flask/app.py handles this:

for func in reversed(self.teardown_appcontext_funcs):
with collect_errors:
self.ensure_sync(func)(exc)

Robust Cleanup with _CollectErrors

Flask ensures that a failure in one teardown function does not prevent others from running. It uses a utility class called _CollectErrors (found in src/flask/helpers.py) to wrap every teardown call.

If a teardown function raises an exception, _CollectErrors catches it and stores it. After all functions have been attempted, Flask raises the collected errors. On Python 3.11 and newer, these are raised as an ExceptionGroup; on older versions, the first error is re-raised.

Nested Contexts and Push Counts

In complex scenarios like testing or streaming, you might push the same AppContext multiple times. Flask tracks this using a _push_count attribute in the AppContext class.

Cleanup logic—including teardown functions and signals—only executes when the _push_count reaches zero. This means that if you manually push a context twice, you must pop it twice before any resources are actually released.

ctx = app.app_context()
ctx.push() # _push_count = 1
ctx.push() # _push_count = 2
ctx.pop() # _push_count = 1, no teardown yet
ctx.pop() # _push_count = 0, teardown functions run now

Teardown Signals

Flask provides two signals for monitoring the teardown process:

  • appcontext_tearing_down: Sent at the beginning of the app context teardown process, before the registered functions are called. It includes the exc argument.
  • appcontext_popped: Sent at the very end of AppContext.pop(), after all cleanup is complete and the context has been removed from the internal stack.

These signals are useful for extensions that need to coordinate their own cleanup with the Flask lifecycle without requiring the user to register a specific teardown function.