Opened 5 months ago
Last modified 5 months ago
#35583 closed Bug
asgiref.sync.sync_to_async cannot be affected by close_old_connections — at Initial Version
Reported by: | Alexandr Onufrienko | Owned by: | |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | |
Severity: | Normal | Keywords: | |
Cc: | Carlton Gibson | Triage Stage: | Unreviewed |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
Rel. to https://code.djangoproject.com/ticket/34914#comment:3
partially rel. to this implementation https://github.com/django/channels/blob/main/channels/db.py
- Let's assume, that we have django command, that creates a "server" that listening for a message from queue, or periodically do some job.
- Second point - our service have async function and requires DB calls (dummy example in _test function)
- let's assume. that we have Postgres DB running in docker compose - and to reproduce this bug, we should call docker compose restart db
Similar to Django itself and, for example, to Django channels https://github.com/django/channels/blob/main/channels/db.py we want to be able to close closed connection (simplest way to reproduce - DB was restarted).
Expected behavior: we are assuming, that we do same steps as we can see in Django project examples and we should see success message in logs.
Actual: we will see failure: the connection is closed
What we can see in code itself:
- sync_to_async class will use its own isolated ThreadPoolExecutor
- django connections will be created withing this ThreadPoolExecutor and will be unavailable for us (this is my assumption)
Found workaround
Calling this code will implicitly have access to hidden DB pool and will close closed connections
syncio.run(sync_to_async(close_old_connections)())
Notes:
- I'm not really sure - is it a feature request or a bug (taking into account, how hard to find theoretical reason of it)
- I didn't dive into details of async_to_sync function/class
Code snippet for Django command
import asyncio import time from asgiref.sync import sync_to_async from django.db import close_old_connections from django.core.management.base import BaseCommand # This can be any model from django.contrib.auth.models import User class Command(BaseCommand): def handle(self, *args, **options): while True: process_message() time.sleep(3) def process_message(): print("close_old_connections") close_old_connections() print("run test") try: test() print("success") except Exception as e: print("failure:", str(e)) print("done test") def test(): asyncio.run(_test()) async def _test(): """ Synthetic case - most likely, we will use ``sync_to_async`` as decorator for part of functions and will use it directly for other calls ``sync_to_async``. """ await User.objects.afirst() await sync_to_async( User.objects.first )() await sync_to_async( User.objects.first, thread_sensitive=False, )()