#35583: asgiref.sync.sync_to_async cannot be affected by close_old_connections
-------------------------------+--------------------------------------
     Reporter:  AlexandrOnuf   |                    Owner:  (none)
         Type:  Uncategorized  |                   Status:  new
    Component:  Uncategorized  |                  Version:
     Severity:  Normal         |               Resolution:
     Keywords:                 |             Triage Stage:  Unreviewed
    Has patch:  0              |      Needs documentation:  0
  Needs tests:  0              |  Patch needs improvement:  0
Easy pickings:  0              |                    UI/UX:  0
-------------------------------+--------------------------------------
Description changed by AlexandrOnuf:

Old 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
>
> 1. Let's assume, that we have django command, that creates a "server"
> that listening for a message from queue, or periodically do some job.
> 2. Second point - our service have async function and requires DB calls
> (dummy example in ''_test'' function)
> 3. 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:**
> 1. sync_to_async class will use its own isolated ThreadPoolExecutor
> 2. 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:
> 1. I'm not really sure - is it a feature request or a bug (taking into
> account, how hard to find theoretical reason of it)
> 2. 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,
>     )()
> }}}

New 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

 1. Let's assume, that we have django command, that creates a "server" that
 listening for a message from queue, or periodically do some job.
 2. Second point - our service have async function and requires DB calls
 (dummy example in ''_test'' function)
 3. 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:**
 1. sync_to_async class will use its own isolated ThreadPoolExecutor
 2. 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

 {{{
 asyncio.run(sync_to_async(close_old_connections)())
 }}}


 Notes:
 1. I'm not really sure - is it a feature request or a bug (taking into
 account, how hard to find theoretical reason of it)
 2. 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,
     )()
 }}}

--
-- 
Ticket URL: <https://code.djangoproject.com/ticket/35583#comment:1>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/01070190918545b2-2fe798cb-c6ac-4721-8652-aa4c35969047-000000%40eu-central-1.amazonses.com.

Reply via email to