#21553 closed Bug (fixed)
InterfaceError in Postgres
Reported by: | anonymous | Owned by: | Aymeric Augustin |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | 1.6 |
Severity: | Release blocker | Keywords: | |
Cc: | depaolim@…, Shai Berger | Triage Stage: | Accepted |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
I think this is a hole in the fix for #15901 -- in Postgres, connection.is_usable accesses the cursor directly, with the result that base exceptions (eg. InterfaceError) are thrown without the normal wrapper and thus not caught:
def is_usable(self): try: # Use a psycopg cursor directly, bypassing Django's utilities. self.connection.cursor().execute("SELECT 1") except DatabaseError: return False else: return True
Change History (24)
comment:1 by , 11 years ago
comment:2 by , 11 years ago
Resolution: | → needsinfo |
---|---|
Status: | new → closed |
Since you reported that ticket anonymously, I'm not even sure you'll see my answer. I'm closing the ticket as "needing more information". Please reopen it with the answer to my questions above.
comment:3 by , 11 years ago
Resolution: | needsinfo |
---|---|
Status: | closed → new |
We are getting this error when the connection has been closed by Postgres:
InterfaceError: connection already closed
There was an earlier ticket (#15802) about this error in particular; from looking at the code, it seems that the fix for the earlier ticket involved catching Database.Error
in close()
but in our case it looks like the error is being raised from is_usable()
which only catches Database.DatabaseError
.
I would argue that is_usable()
should never throw; regardless of what error occurs, the result is that the database is not usable.
comment:4 by , 11 years ago
The use case for this fix is picking up a new connection after restarting postgresql (or in my case, pgpool2, but this doesn't seem to matter).
To replicate this error, simply run your app, bring the database down, send one more request, then bring the database back up. The backend will start throwing psycopg2.InterfaceError.
To my understanding, both DatabaseError and InterfaceError are reasonable there, one means you couldn't connect, the other means you couldn't _re_connect.
FWIW, the fix to this is simply change the line to be except (DatabaseError, Database.InterfaceError):
comment:5 by , 11 years ago
Is this a duplicate of #21202? There is a patch there that may be helpful.
comment:6 by , 11 years ago
I just ran into this as well.
For some reason postmaster killed a process and so all of postgres restarted to avoid corruption. Postgres was down for only a few seconds and normal service should have resumed.
However, all the persistent db connections in Django were now dead and needed to reconnect. But is_usable was throwing "InterfaceError: connection already closed" and so Django would never reconnect and only served error 500 pages until I manually restarted it.
comment:8 by , 11 years ago
The fix proposed in comment ticket:21553#comment:4 works great.
I was having the exact same issue but ONLY when using persistent connections with Postgres.
comment:9 by , 11 years ago
Owner: | changed from | to
---|---|
Status: | new → assigned |
comment:10 by , 11 years ago
Triage Stage: | Unreviewed → Accepted |
---|---|
Type: | Uncategorized → Bug |
comment:11 by , 11 years ago
Cc: | added |
---|
follow-up: 20 comment:12 by , 11 years ago
This issue took down my production sites twice until I manually restarted. I'd argue that persistent connections with PostgreSQL (and potentially MySQL) have to be considered broken and that this should be escalated to release blocker.
I'd follow adam-iris's argument that is_usable should never throw an exception, and default to returning False. I'd argue that catching all exceptions is reasonable in this case, but am aware it's considered bad style. Either way, InterfaceError needs to be caught at least in the PostgreSQL backend. I'm not sure if MySQL's backend's ping() can throw an InterfaceError, that would be worth a look.
I tried to reproduce this locally, but did not manage so far. The downtimes were caused by Heroku restarting the PostgreSQL service, from which Django should be able to recover gracefully.
comment:13 by , 11 years ago
Severity: | Normal → Release blocker |
---|
comment:14 by , 11 years ago
Resolution: | → fixed |
---|---|
Status: | assigned → closed |
comment:17 by , 11 years ago
Cc: | added |
---|---|
Patch needs improvement: | set |
Resolution: | fixed |
Status: | closed → new |
The test introduced in 1.6.x is broken with Python 2.7 -- it uses a cursor as a context manager, and on 2.7 that only works with Django 1.7.
The block that uses it is empty:
with connection.cursor(): pass
the with
here doesn't really handle any exceptions; I think that on Django>=1.7, this block is equivalent to connection.cursor().close()
, which would also work on 1.6.
comment:19 by , 11 years ago
Patch needs improvement: | unset |
---|---|
Resolution: | → fixed |
Status: | new → closed |
comment:20 by , 11 years ago
Replying to maikhoepfel:
I tried to reproduce this locally, but did not manage so far. The downtimes were caused by Heroku restarting the PostgreSQL service, from which Django should be able to recover gracefully.
If you tested with runserver, it opens a new connection for each request, because connections are thread local and it uses a new thread for each connection. You must use ./manage.py runserver --nothreading
to test this feature.
comment:21 by , 10 years ago
We are not using persistent connections, but every once in a while we are getting InterfaceError.
This occasionally happens just after PostgreSQL server is restarted and sometimes it just happends during regular operations.
is_usable
is supposed to check if connection is alive: return false if it's not.
InterfaceError clearly says it's not usable, why would we raise an exception instead of returning false?
Fix is easy: https://github.com/mkorenkov/django/commit/ddb3083c3959e3b30f2c0821d0ba1fec7d34a834 and users are suffering.
comment:22 by , 10 years ago
Resolution: | fixed |
---|---|
Status: | closed → new |
Triage Stage: | Accepted → Someday/Maybe |
reopening based on the previous comment.
we were getting InterfaceError in is_usable
method. Please check the fix above.
comment:23 by , 10 years ago
Resolution: | → fixed |
---|---|
Status: | new → closed |
Triage Stage: | Someday/Maybe → Accepted |
The fix isn't made at the right level.
The problem you're seeing is a new issue: an unwrapped exception is reaching the Django layers. It may be a duplicate of a variant of #22879.
Can you open a new ticket with the full stack trace? That will allow us to address the root cause rather than the symptom in one particular place (leaving other places vulnerable). Thank you!
comment:24 by , 10 years ago
The problem you're seeing is a new issue: an unwrapped exception is reaching the Django layers.
I am quite OK, if InterfaceError would be handled somewhere at connection.cursor().execute
and re-raised as a DatabaseError.
Sorry, I would have to switch the production code to the version without the fix to get the stack trace, which is not really an option.
InterfaceError
means that something is wrong with the database adapter on the Python side. I'm not convinced it's safe or useful to attempt automatically reconnecting to the database in that case.That's why I chose to catch
DatabaseError
and notInterfaceError
.Could you explain your use case? In what circumstances do you receive
InterfaceError
?