#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.