#34737: Python 3.10 compatibility changes to utils/asyncio.py raise
SynchronousOnlyOperation for non-running event loops on python >= 3.7
---------------------------------------+------------------------
               Reporter:  wbastian-bh  |          Owner:  nobody
                   Type:  Bug          |         Status:  new
              Component:  Utilities    |        Version:  3.2
               Severity:  Normal       |       Keywords:
           Triage Stage:  Unreviewed   |      Has patch:  0
    Needs documentation:  0            |    Needs tests:  0
Patch needs improvement:  0            |  Easy pickings:  0
                  UI/UX:  0            |
---------------------------------------+------------------------
 **Env**
 Django 3.2.9+
 Python 3.7+

 **Overview**
 With this commit
 
(https://github.com/django/django/commit/53fad80ffe16ab4edb713b1ef0090d0fcf63565a),
 which was included with the 3.2.9 release if we're on PY3.7+, we raise
 SynchronousOnlyOperation when asyncio.get_running_loop returns an object
 without checking event_loop.is_running().

 It appears that asyncio.get_running_loop can return non-running loops, as
 observed by including a logging statement before raising the
 SynchronousOnlyOperation.

 If my understanding is correct, get_running_loop should only be returning
 running loops, and is not.

 Curious if we can continue to leverage event_loop.is_running() in all
 cases.

 **Observation Example**
 {{{
 import asyncio
 import functools
 import os
 ​
 from django.core.exceptions import SynchronousOnlyOperation
 from django.utils.version import PY37
 ​
 ​
 if PY37:
     get_running_loop = asyncio.get_running_loop
 else:
     get_running_loop = asyncio.get_event_loop
 ​
 ​
 def async_unsafe(message):
     """
     Decorator to mark functions as async-unsafe. Someone trying to access
     the function while in an async context will get an error message.
     """
     def decorator(func):
         @functools.wraps(func)
         def inner(*args, **kwargs):
             if not os.environ.get('DJANGO_ALLOW_ASYNC_UNSAFE'):
                 # Detect a running event loop in this thread.
                 try:
                     event_loop = get_running_loop()
                 except RuntimeError:
                     pass
                 else:
                     if PY37 or event_loop.is_running():
                         print(f"raising SynchronousOnlyOperation on
 {event_loop} where is_running = {event_loop.is_running()}")
                         raise SynchronousOnlyOperation(message)
             # Pass onwards.
             return func(*args, **kwargs)
         return inner
     # If the message is actually a function, then be a no-arguments
 decorator.
     if callable(message):
         func = message
         message = 'You cannot call this from an async context - use a
 thread or sync_to_async.'
         return decorator(func)
     else:
         return decorator
 }}}

 **Observation Output**
 {{{
 raising SynchronousOnlyOperation on <_UnixSelectorEventLoop running=False
 closed=False debug=False> where is_running = False
 }}}

 **Steps to reproduce**
 1. Have a non-running event loop present and do just about anything in
 Django
 2. See SynchronousOnlyOperation raised

-- 
Ticket URL: <https://code.djangoproject.com/ticket/34737>
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/0107018988096cfb-1fdde7e8-9d9c-474a-a983-fd3eb4ddd7a8-000000%40eu-central-1.amazonses.com.

Reply via email to