Description
We are using pg8000 with SQLAlchemy in a docker environment. Everything works fine but while testing the application, whenever I restart the database(Postgresql) to simulate some error, the flask application cannot connect to the database. I'm getting either BrokenPipeError
or struct.error
. I found in the README of pg8000 this behaviour is mentioned. We tried to use SQLAlchemy's pre-pinging to deal with disconnects. But raising these exceptions seems to not invalidate the connection. Here is a sample log
2021-03-16 13:55:19,928 DEBUG sqlalchemy.pool.impl.QueuePool Connection <pg8000.legacy.Connection object at 0x7ff0bbac4150> checked out from pool
2021-03-16 13:55:19,928 DEBUG sqlalchemy.pool.impl.QueuePool Pool pre-ping on connection <pg8000.legacy.Connection object at 0x7ff0bbac4150>
[2021-03-16 13:55:19,929] ERROR in app: Exception on /namespaces/ [GET]
Traceback (most recent call last):
File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1950, in full_dispatch_request
rv = self.dispatch_request()
File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1936, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/usr/local/lib/python3.7/site-packages/flask_restx/api.py", line 375, in wrapper
resp = resource(*args, **kwargs)
File "/usr/local/lib/python3.7/site-packages/flask/views.py", line 89, in view
return self.dispatch_request(*args, **kwargs)
File "/usr/local/lib/python3.7/site-packages/flask_restx/resource.py", line 44, in dispatch_request
resp = meth(*args, **kwargs)
File "/usr/local/lib/python3.7/site-packages/flask_httpauth.py", line 382, in decorated
)(f)(*args, **kwargs)
File "/usr/local/lib/python3.7/site-packages/flask_httpauth.py", line 149, in decorated
user = self.authenticate(auth, password)
File "/usr/local/lib/python3.7/site-packages/flask_httpauth.py", line 220, in authenticate
return self.verify_password_callback(username, client_password)
File "./core/model.py", line 104, in verify_password
rci. Suspendisse ac accumsan ipsum. Suspendisse auctor venenatis magna
File "/usr/local/lib/python3.7/site-packages/sqlalchemy/orm/query.py", line 2684, in first
return self.limit(1)._iter().first()
File "/usr/local/lib/python3.7/site-packages/sqlalchemy/orm/query.py", line 2771, in _iter
execution_options={"_sa_orm_load_options": self.load_options},
File "/usr/local/lib/python3.7/site-packages/sqlalchemy/orm/session.py", line 1652, in execute
conn = self._connection_for_bind(bind, close_with_result=True)
File "/usr/local/lib/python3.7/site-packages/sqlalchemy/orm/session.py", line 1503, in _connection_for_bind
engine, execution_options
File "/usr/local/lib/python3.7/site-packages/sqlalchemy/orm/session.py", line 738, in _connection_for_bind
conn = bind.connect()
File "/usr/local/lib/python3.7/site-packages/sqlalchemy/engine/base.py", line 3066, in connect
return self._connection_cls(self, close_with_result=close_with_result)
File "/usr/local/lib/python3.7/site-packages/sqlalchemy/engine/base.py", line 91, in __init__
else engine.raw_connection()
File "/usr/local/lib/python3.7/site-packages/sqlalchemy/engine/base.py", line 3145, in raw_connection
return self._wrap_pool_connect(self.pool.connect, _connection)
File "/usr/local/lib/python3.7/site-packages/sqlalchemy/engine/base.py", line 3112, in _wrap_pool_connect
return fn()
File "/usr/local/lib/python3.7/site-packages/sqlalchemy/pool/base.py", line 301, in connect
return _ConnectionFairy._checkout(self)
File "/usr/local/lib/python3.7/site-packages/sqlalchemy/pool/base.py", line 781, in _checkout
result = pool._dialect.do_ping(fairy.connection)
File "/usr/local/lib/python3.7/site-packages/sqlalchemy/engine/default.py", line 648, in do_ping
cursor.execute(self._dialect_specific_select_one)
File "/usr/local/lib/python3.7/site-packages/pg8000/legacy.py", line 175, in execute
self._c.execute_unnamed("begin transaction")
File "/usr/local/lib/python3.7/site-packages/pg8000/core.py", line 735, in execute_unnamed
self.handle_messages(context)
File "/usr/local/lib/python3.7/site-packages/pg8000/core.py", line 895, in handle_messages
code, data_len = ci_unpack(self._read(5))
struct.error: unpack_from requires a buffer of at least 5 bytes
2021-03-16 13:55:19,933 DEBUG sqlalchemy.pool.impl.QueuePool Connection <pg8000.legacy.Connection object at 0x7ff0bbac4150> being returned to pool
2021-03-16 13:55:19,933 DEBUG sqlalchemy.pool.impl.QueuePool Connection <pg8000.legacy.Connection object at 0x7ff0bbac4150> rollback-on-return
I created a minimal example to reproduce the issue.
git pull https://github.com/hbusul/sqlalchemy_pg800_pre_ping
docker-compose up --build
After 10 secs, from another terminal docker-compose restart database
I already opened a ticket in pg8000
I guess these exceptions should be wrapped in other exceptions. My question is that which exception would be suitable for the issue?
Versions
- OS: Linux
- Python: 3.7
- SQLAlchemy: 1.3.5
- Database: Postgresql
- DBAPI: pg8000?
Activity
zzzeek commentedon Mar 19, 2021
this is very poor behavior on the part of pg8000 and IMO it should be fixed on their end. When the database connection has died, the appropriate DBAPI error to raise is OperationalError.
zzzeek commentedon Mar 19, 2021
can confirm same exact error here just running the script and restarting PG
Any reason you aren't able to use psycopg2?
hbusul commentedon Mar 19, 2021
I agree that this should be fixed on
pg8000
, I wanted to verify the correct behavior.We started using it a bit while ago and it works quite ok so we did not feel the necessity to change it. But if fixing this issue as easy as wrapping that with
OperationalError
, I guess we can wait for the fix and everyone else usingpg8000
would benefit as well.Thanks for verifying the correct behaviour!
hbusul commentedon Mar 28, 2021
@zzzeek in pg8000 now the exceptions are wrapped in
InterfaceError
. And I guess the reason is that the native interface only hasInterfaceError
andDatabaseError
. But currently, those changes still do not invalidate the connection and I guess that's not due to type of the exception but due to exception message. I found in documentation:And I found pg8000.py
I guess this string never appears in
pg8000
repository. And given the fix commit, usesnetwork error on read
,network error on write
andnetwork error on flush
, @tlocke suggested that we can useI already tried changing those error messages in
pg8000
and it does work.@zzzeek Do you think changing this function is OK?
zzzeek commentedon Mar 28, 2021
meaning, they fixed the issue ? did they release?
it's fine, lots of DBAPIs use InterfaceError and OperationalError interchangeably, the pep is much too vague
yes this is the appropriate action to take on the SQLAlhcemy side, we can also assert that the exception object is an instance of
InterfaceError
. we can accept a PR for this now that pg8000 has defined how they will handle this.tlocke commentedon Mar 28, 2021
Hi all, just to confirm, I've just done a new release of pg8000 (1.19.0) that wraps all network error exceptions in an
InterfaceError
with a message that starts,network error
. The original exception is set as the cause.update pg8000 dialect is_disconnect method Fixes: sqlalchemy#6099
17 remaining items