Debugging “Instance XXX is not bound to a Session” Errors in Gevent‑Enabled Flask APIs with SQLAlchemy
This article analyzes the intermittent “Instance XXX is not bound to a Session” error that occurs after converting a Flask‑SQLAlchemy endpoint from serial to multithreaded/gevent concurrency, reproduces the issue with test code, explains the root cause in session handling, and provides a concrete fix by patching gevent before session initialization.
Background: After changing a high‑traffic API to use multithreaded concurrent queries, logs occasionally showed the error Instance XXX is not bound to a Session; attribute refresh operation cannot proceed .
Reproduction: The problem could not be reproduced in offline regression tests, so a local environment was built using the following test script:
import gevent
from gevent import monkey
import threading
monkey.patch_all()
with app.test_client() as c:
def _exec(_id):
req_data = {...}
rv = c.post('/api/v1/A', json=req_data)
print(rv.get_json())
assert rv.status_code == 200
ts = []
for i in range(2):
ts.append(gevent.spawn(_exec, i))
for t in ts:
t.join()The test simulates the production stack Gunicorn (gevent mode) → Flask API . The core logic of endpoint A is:
1 db_session = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine))
2 @blueprint.route("/A", methods=["POST"])
3 def A(*args, **kwargs):
4 params = request.get_json()
5 valid(params)
6 b = create_orm_obj_b(params)
7 db_session.add(b)
8 multiple_threads_query_database()
9 try:
10 db_session.commit()
11 except Exception as ex:
12 db_session.rollback()
13 return 500
14 return 200Increasing the number of gevent greenlets to three reproduced the error, while running the same code with pure threads (no gevent) never triggered it, pointing to the gevent library.
Analysis: The SQLAlchemy error page explains that the ORM object b became detached from its Session after a commit/flush, causing lazy‑loaded attribute access to fail. Investigation showed that two concurrent requests shared the same scoped session, so the commit of the first request flushed the second request’s object, leaving it detached.
Further digging revealed that scoped_session relies on thread‑local storage (TLS) by default. When Gunicorn runs in gevent mode, gevent.monkey.patch_all() replaces TLS with gevent.local , causing the same session object to be reused across greenlets.
Fix: Apply gevent.monkey.patch_all() **before** creating the scoped session, ensuring each greenlet gets its own session, or avoid using a global session altogether.
Conclusion: When using Gevent with SQLAlchemy, patch gevent early and avoid sharing a global Session; otherwise, detached‑object errors like “Instance not bound to a Session” will appear under concurrency.
NetEase Game Operations Platform
The NetEase Game Automated Operations Platform delivers stable services for thousands of NetEase titles, focusing on efficient ops workflows, intelligent monitoring, and virtualization.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.