This is to revisit an old issue from the tracker, #30646 (close_if_unusable_or_obsolete fails to close unusable connections.) – Django.
I recently came across this on a production Celery worker that uses the soft_time_limit feature. Celery uses the python signal module to hijack control of a task and raise an Exception to prevent runaway tasks.
This can be reproduced without Celery though:
- Create a view at
/in a Django application using a postgres database withCONN_MAX_AGEconfigured to be greater than the duration of the experiment (say ~60)
import signal
import threading
from django.db import connection
def interruption_handler(*args):
raise Exception("Interruption")
signal.signal(signal.SIGALRM, interruption_handler)
def test_view(request):
def hijack_control_flow():
signal.alarm(2)
thread = threading.Thread(target=hijack_control_flow)
thread.start()
with connection.cursor() as cursor:
# this query will be interrupted by interruption_handler
cursor.execute("SELECT pg_sleep(5);")
- Run the application using
gunicorn --error-logfile /dev/stdout -w1 --bind localhost:8000 testapp.wsgi - Run
curl http://localhost:8000and note the regularException: Interruption - Run
curl http://localhost:8000again and notedjango.db.utils.OperationalError: sending query failed: another command is already in progress
The second request will never be able to use the database because it’s attempting to use an unusable connection.
I see 3 paths forward:
- Keep this as
wontfixand have downstreams deal with it, perhaps pushing a PR to Celery to stop trying to reuse connections between tasks. - Fully check if the connection is usable each request (see Fixed #30646 -- Always close unusable connections by dneuhaeuser-zalando · Pull Request #11573 · django/django · GitHub).
- Modify
close_if_unusable_or_obsoleteto take an additional argument to more rigorously check for usability.
2 seems too expensive for such an edge case, and possibly would still allow the bug to exist in the case of a database failover as reported in #30646. I’m leaning towards the first path. Does anyone have additional thoughts?