2025, Dec 19 09:00

Fix Flask 3.0 performance and socket leaks: manage a shared requests.Session with before_serving/after_serving

Speed up a Flask 3.0 Python 3.12 service calling a REST API by reusing one requests.Session with serving lifecycle hooks to prevent socket leaks and warnings.

Building a Flask 3.0 micro-service on Python 3.12 that calls an external REST API on nearly every request quickly exposes a performance and stability pitfall. If each route constructs its own requests.Session, responses slow down and sockets start leaking under load. Switching to a bare global session may look tempting, but that approach surfaces a resource warning and still leaves lifecycle management unclear. The goal is straightforward: reuse a single requests.Session across all incoming requests and close it exactly once when the application shuts down.

Problem code

The pattern below creates a new session for every request, which is slow and leads to socket leaks under pressure.

from flask import Flask, jsonify
import requests
svc = Flask(__name__)
@svc.get("/info")
def fetch_info():
    with requests.Session() as http:
        resp = http.get("https://api.example.com/info")
    return jsonify(resp.json())

What actually goes wrong

Each route invocation spins up a fresh requests.Session and tears it down immediately after, which is inefficient and, under load, leaks sockets. Replacing it with a plain global session object triggers a resource warning. In other words, neither per-request construction nor an unmanaged global solves the problem of safe reuse and deterministic shutdown.

Solution

Use Flask serving-lifecycle hooks that run exactly when needed. The @app.before_serving hook runs once per worker right before the first request is accepted, which is the right moment to create a shared requests.Session. The @app.after_serving hook runs once on a clean shutdown, which is the right moment to close that session exactly once. Store the session on the application object so it’s available to all routes in the same worker.

Corrected code

from flask import Flask, jsonify
import requests
service = Flask(__name__)
@service.before_serving
def prepare_http_client():
    service.shared_session = requests.Session()
@service.after_serving
def teardown_http_client():
    service.shared_session.close()
@service.get("/info")
def read_info():
    reply = service.shared_session.get("https://api.example.com/info")
    return jsonify(reply.json())

Why this matters

This approach reuses a single session for all requests handled by a worker, avoids the per-request overhead that slows down responses, and prevents the socket leaks observed under load. It also removes the resource warning that appears with an unmanaged global by tying session lifetime to the application lifecycle: created once before serving and closed once on clean shutdown.

Takeaways

When a Flask 3.0 service needs to call an external REST API frequently, avoid creating a new requests.Session in each route and avoid unscoped globals. Put the session on the application object and manage it with @app.before_serving and @app.after_serving. This ensures predictable reuse and a single, clean close when the process stops.