Hi all,
I'm using sqlalchemy 0.9.7 (yes, I know we need to upgrade).
I've recently started to employ psycopg2's
psycopg2.extensions.set_wait_callback function to facilitate terminating
ongoing queries in response to activity on other sockets (such as an
HTTP client disconnect or shutdown message). I essentially have this
working, but there is a wrinkle.
Specifically, when it is time to abort the ongoing query, I arrange for
the wait_callback to raise an exception. psycopg2 dutifully closes the
dbapi connection when an exception occurs, which is what I want it to
do. The problem is, sqlalchemy still thinks the connection is open at
the Connection and ConnectionFairy layers and sometimes runs into a
variety of "connection closed" errors when sqlalchemy tries to do stuff
with the dbapi connection later because it thinks it is still open.
What I want to do is call Connection.invalidate() from inside the
wait_callback, in order to invalidate the Connection, ConnectionRecord,
and ConnectionFairy. This is difficult because the only parameter to
the wait_callback function is the DBAPI connection, not the associated
high level connection object(s). I have no obvious way of getting from
the DBAPI connection to the SA Connection object(s).
I've had a few ideas on how to solve this, one of which seems to work in
my limited testing, but the documentation gives me second thoughts. I
wonder if there is a better way.
Approach 1
The first thing I tried (which did not work at all) was to use:
event.listens_for(SomeEngine, 'handle_error')
in order that I could set the is_disconnect field on the
exception_context object, thereby notifying sqlalchemy of the
disconnect. Turns out that sqlalchemy doesn't wrap calls to fetchmany
such that exceptions from fetchmany get sent to handle_error, so I
abandoned this approach. I wonder if this is indicative of a more
general issue where network errors raised by fetchmany aren't noticed?
Approach 2
The next thing I tried was using:
@event.listens_for(SomeEngine, 'engine_connect')
def receive_engine_connect(conn, branch):
raw = conn.connection.connection
if not hasattr(raw, 'sa_conns'):
raw.sa_conns = weakref.WeakSet()
raw.sa_conns.add(conn)
And then when I know the dbapi connection is about to be closed for
sure, I do something like this (but more carefully):
for x in getattr(dbapi_conn, 'sa_conns', ()):
x.invalidate()
This seemed to work in practice, but this sentence from the
documentation and the body of Connection._revalidate_connection() make
me think existing Connection objects can get reassociated with different
dbapi connections without my knowledge:
"But note there can in fact be multiple PoolEvents.checkout() events
within the lifespan of a single Connection object, if that Connection is
invalidated and re-established."
Approach 3
This is similar to approach 2, except that I use the "checkout" event
instead. This allows me to invoke ConnectionFairy.invalidate() or
ConnectionRecord.invalidate(), but that seems to not be as good as
invoking Connection.invalidate(), because the Connection still doesn't
realize it is closed.
Is there something better I can do to get a mapping from DBAPI
connection to SA Connection? I am inclined to do linear search over all
Connection objects for ones using the dying dbapi connection. I assume
the engine or pool must have a collection of all Connections, and there
aren't ever more than a few in my application.
- Dave
--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper
http://www.sqlalchemy.org/
To post example code, please provide an MCVE: Minimal, Complete, and Verifiable
Example. See http://stackoverflow.com/help/mcve for a full description.
---
You received this message because you are subscribed to the Google Groups "sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.