-
-
Notifications
You must be signed in to change notification settings - Fork 207
Description
Hello there,
The custom wait_c implementation does not implement EINTR handling in the same way that the standard library does as of Python 3.5 / PEP-475 (automatic retries).
This is at the root of a problem we've seen pop up seemingly at random, and rather upsettingly it was obfuscated behind a completely different error when combined with SQLAlchemy sessions and connection pooling.
Eventually we worked out that the signals from our wall profiler were manifesting as an EINTR error, and that psycopg's wait implementation wasn't retrying when they occur. Swapping to the native Python implementation with PSYCOPG_WAIT_FUNC=wait_poll solves the problem.
The result is an Exception that looks something like this
File "/app/.venv/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 2232, in execute
return self._execute_internal(
^^^^^^^^^^^^^^^^^^^^^^^
File "/app/.venv/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 2127, in _execute_internal
result: Result[Any] = compile_state_cls.orm_execute_statement(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/.venv/lib/python3.11/site-packages/sqlalchemy/orm/context.py", line 293, in orm_execute_statement
result = conn.execute(
^^^^^^^^^^^^^
File "/app/.venv/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1413, in execute
return meth(
^^^^^
File "/app/.venv/lib/python3.11/site-packages/sqlalchemy/sql/elements.py", line 483, in _execute_on_connection
return connection._execute_clauseelement(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/.venv/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1637, in _execute_clauseelement
ret = self._execute_context(
^^^^^^^^^^^^^^^^^^^^^^
File "/app/.venv/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1846, in _execute_context
return self._exec_single_context(
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/.venv/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1987, in _exec_single_context
self._handle_dbapi_exception(
File "/app/.venv/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 2347, in _handle_dbapi_exception
raise exc_info[1].with_traceback(exc_info[2])
File "/app/.venv/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1968, in _exec_single_context
self.dialect.do_execute(
File "/app/.venv/lib/python3.11/site-packages/sqlalchemy/engine/default.py", line 920, in do_execute
cursor.execute(statement, parameters)
File "/app/.venv/lib/python3.11/site-packages/psycopg/cursor.py", line 719, in execute
self._conn.wait(
File "/app/.venv/lib/python3.11/site-packages/psycopg/connection.py", line 957, in wait
return waiting.wait(gen, self.pgconn.socket, timeout=timeout)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "psycopg_binary/_psycopg/waiting.pyx", line 179, in psycopg_binary._psycopg.wait_c
InterruptedError: [Errno 4] Interrupted system call
For posterity, the default FastAPI/SQLAlchemy pattern will result in an Exception that swallows the one above:
session.close()
File "/app/.venv/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 2383, in close
self._close_impl(invalidate=False)
File "/app/.venv/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 2425, in _close_impl
transaction.close(invalidate)
File "<string>", line 2, in close
File "/app/.venv/lib/python3.11/site-packages/sqlalchemy/orm/state_changes.py", line 137, in _go
ret_value = fn(self, *arg, **kw)
^^^^^^^^^^^^^^^^^^^^
File "/app/.venv/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 1330, in close
transaction.close()
File "/app/.venv/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 2577, in close
self._do_close()
File "/app/.venv/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 2715, in _do_close
self._close_impl()
File "/app/.venv/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 2701, in _close_impl
self._connection_rollback_impl()
File "/app/.venv/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 2693, in _connection_rollback_impl
self.connection._rollback_impl()
File "/app/.venv/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1117, in _rollback_impl
self._handle_dbapi_exception(e, None, None, None, None)
File "/app/.venv/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 2344, in _handle_dbapi_exception
raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
File "/app/.venv/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1115, in _rollback_impl
self.engine.dialect.do_rollback(self.connection)
File "/app/.venv/lib/python3.11/site-packages/sqlalchemy/engine/default.py", line 693, in do_rollback
dbapi_connection.rollback()
File "/app/.venv/lib/python3.11/site-packages/psycopg/connection.py", line 889, in rollback
self.wait(self._rollback_gen())
File "/app/.venv/lib/python3.11/site-packages/psycopg/connection.py", line 957, in wait
return waiting.wait(gen, self.pgconn.socket, timeout=timeout)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "psycopg_binary/_psycopg/waiting.pyx", line 176, in psycopg_binary._psycopg.wait_c
File "/app/.venv/lib/python3.11/site-packages/psycopg/connection.py", line 557, in _rollback_gen
yield from self._exec_command(b"ROLLBACK")
File "/app/.venv/lib/python3.11/site-packages/psycopg/connection.py", line 461, in _exec_command
self.pgconn.send_query_params(command, None, result_format=result_format)
File "psycopg_binary/pq/pgconn.pyx", line 276, in psycopg_binary.pq.PGconn.send_query_params
sqlalchemy.exc.OperationalError: (psycopg.OperationalError) sending query and params failed: another command is already in progress
(Background on this error at: https://sqlalche.me/e/20/e3q8)
Using an SQLAlchemy session contextually produces a similar stack:
with Session() as session:
File "/app/.venv/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 1687, in __exit__
self.close()
File "/app/.venv/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 2383, in close
self._close_impl(invalidate=False)
... <snipped>