Канал Django периодически получает ошибку "cannot call this from an async context - use a thread or sync_to_async".
Мой проект недавно добавил django channel для websocket и layer:
Django=3.2.6
Channel=3.0.4
channels-redis="3.3.0"
gevent=21.8.0
gunicorn=20.1.0
Gunicorn и daphne запускают WSGI и ASGI отдельно, Nginx проксирует трафик на WSGI или ASGI. Это часть конфигурации супервизора
[program:myproj]
command=/usr/local/bin/gunicorn -c gunicorn_conf.py myproj.wsgi
[fcgi-program:myproj_channel]
socket=tcp://0.0.0.0:8002
directory=/opt/www/myproj
command=daphne -u /var/run/daphne/daphne%(process_num)d.sock --fd 0 --access-log - --proxy-headers myproj.asgi:application
В моей производственной среде вероятность получить cannot call this from an async context - use a thread or sync_to_async
составляет менее 0.1%. Эти ошибки не от ASGI(daphne), а от обычных WSGI(Gunicorn) запросов, они возникли не в одном и том же куске кода, но первопричиной является запрос к базе данных.
Например, это в запросе к базе данных:
Traceback (most recent call last):
File "/usr/local/lib/python3.8/dist-packages/django/core/handlers/exception.py", line 47, in inner
response = get_response(request)
File "/usr/local/lib/python3.8/dist-packages/django/core/handlers/base.py", line 181, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/usr/local/lib/python3.8/dist-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
return view_func(*args, **kwargs)
File "/usr/local/lib/python3.8/dist-packages/django/views/generic/base.py", line 70, in view
return self.dispatch(request, *args, **kwargs)
File "/usr/local/lib/python3.8/dist-packages/rest_framework/views.py", line 509, in dispatch
response = self.handle_exception(exc)
File "/usr/local/lib/python3.8/dist-packages/rest_framework/views.py", line 469, in handle_exception
self.raise_uncaught_exception(exc)
File "/usr/local/lib/python3.8/dist-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
raise exc
File "/usr/local/lib/python3.8/dist-packages/rest_framework/views.py", line 506, in dispatch
response = handler(request, *args, **kwargs)
File "/opt/www/myproj/myproj/services/member_service.py", line 275, in _wrapped_view
return view_func(api_view, request, *args, **kwargs)
File "/opt/www/myproj/myproj/api/restful/form.py", line 108, in get
published_form = PublishedForm.objects.get_latest_published_for_form(form_identifier, try_master_form=False)
File "/opt/www/myproj/myproj/webform/models.py", line 44, in get_latest_published_for_form
return self.filter(form_case__identifier=form_identifier, form_case__form_state=FORM_STATE_PUBLISHED).latest('version')
File "/usr/local/lib/python3.8/dist-packages/django/db/models/query.py", line 670, in latest
return self.reverse()._earliest(*fields)
File "/usr/local/lib/python3.8/dist-packages/django/db/models/query.py", line 664, in _earliest
return obj.get()
File "/usr/local/lib/python3.8/dist-packages/django/db/models/query.py", line 431, in get
num = len(clone)
File "/usr/local/lib/python3.8/dist-packages/django/db/models/query.py", line 262, in __len__
self._fetch_all()
File "/usr/local/lib/python3.8/dist-packages/django/db/models/query.py", line 1324, in _fetch_all
self._result_cache = list(self._iterable_class(self))
File "/usr/local/lib/python3.8/dist-packages/django/db/models/query.py", line 51, in __iter__
results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
File "/usr/local/lib/python3.8/dist-packages/django/db/models/sql/compiler.py", line 1173, in execute_sql
cursor = self.connection.cursor()
File "/usr/local/lib/python3.8/dist-packages/django/utils/asyncio.py", line 24, in inner
raise SynchronousOnlyOperation(message)
Это при сохранении сеанса
Traceback (most recent call last):
File "/usr/local/lib/python3.8/dist-packages/django/core/handlers/exception.py", line 47, in inner
response = get_response(request)
File "/usr/local/lib/python3.8/dist-packages/django/utils/deprecation.py", line 119, in __call__
response = self.process_response(request, response)
File "/usr/local/lib/python3.8/dist-packages/django/contrib/sessions/middleware.py", line 61, in process_response
request.session.save()
File "/usr/local/lib/python3.8/dist-packages/django/contrib/sessions/backends/db.py", line 81, in save
return self.create()
File "/usr/local/lib/python3.8/dist-packages/django/contrib/sessions/backends/db.py", line 51, in create
self._session_key = self._get_new_session_key()
File "/usr/local/lib/python3.8/dist-packages/django/contrib/sessions/backends/base.py", line 196, in _get_new_session_key
if not self.exists(session_key):
File "/usr/local/lib/python3.8/dist-packages/django/contrib/sessions/backends/db.py", line 47, in exists
return self.model.objects.filter(session_key=session_key).exists()
File "/usr/local/lib/python3.8/dist-packages/django/db/models/query.py", line 808, in exists
return self.query.has_results(using=self.db)
File "/usr/local/lib/python3.8/dist-packages/django/db/models/sql/query.py", line 552, in has_results
return compiler.has_results()
File "/usr/local/lib/python3.8/dist-packages/django/db/models/sql/compiler.py", line 1145, in has_results
return bool(self.execute_sql(SINGLE))
File "/usr/local/lib/python3.8/dist-packages/django/db/models/sql/compiler.py", line 1173, in execute_sql
cursor = self.connection.cursor()
File "/usr/local/lib/python3.8/dist-packages/django/utils/asyncio.py", line 24, in inner
raise SynchronousOnlyOperation(message)
Это на сеансе чтения:
Traceback (most recent call last):
File "/usr/local/lib/python3.8/dist-packages/django/contrib/sessions/backends/base.py", line 233, in _get_session
return self._session_cache
During handling of the above exception ('SessionStore' object has no attribute '_session_cache'), another exception occurred:
File "/usr/local/lib/python3.8/dist-packages/django/core/handlers/exception.py", line 47, in inner
response = get_response(request)
File "/opt/www/myproj/myproj/common/middleware.py", line 24, in __call__
if 'prefer_locale' not in request.session:
File "/usr/local/lib/python3.8/dist-packages/django/contrib/sessions/backends/base.py", line 55, in __contains__
return key in self._session
File "/usr/local/lib/python3.8/dist-packages/django/contrib/sessions/backends/base.py", line 238, in _get_session
self._session_cache = self.load()
File "/usr/local/lib/python3.8/dist-packages/django/contrib/sessions/backends/db.py", line 43, in load
s = self._get_session_from_db()
File "/usr/local/lib/python3.8/dist-packages/django/contrib/sessions/backends/db.py", line 32, in _get_session_from_db
return self.model.objects.get(
File "/usr/local/lib/python3.8/dist-packages/django/db/models/manager.py", line 85, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/usr/local/lib/python3.8/dist-packages/django/db/models/query.py", line 431, in get
num = len(clone)
File "/usr/local/lib/python3.8/dist-packages/django/db/models/query.py", line 262, in __len__
self._fetch_all()
File "/usr/local/lib/python3.8/dist-packages/django/db/models/query.py", line 1324, in _fetch_all
self._result_cache = list(self._iterable_class(self))
File "/usr/local/lib/python3.8/dist-packages/django/db/models/query.py", line 51, in __iter__
results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
File "/usr/local/lib/python3.8/dist-packages/django/db/models/sql/compiler.py", line 1173, in execute_sql
cursor = self.connection.cursor()
File "/usr/local/lib/python3.8/dist-packages/django/utils/asyncio.py", line 24, in inner
raise SynchronousOnlyOperation(message)
В моем проекте нет специального промежуточного ПО для канала - мой проект работает годами, только недавно добавил канал, поэтому нет многих настроек для канала.
Installed Middleware:
['myproj.common.middleware.SameSiteMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django_otp.middleware.OTPMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'myproj.common.middleware.MembershipMiddleware',
'myproj.common.middleware.AutoLogoutMiddleware']
Я уверен, что в кодах этих частей нет методов async. Потому что кроме кода websocket, я нашел только одно место, использующее asyncio (и то в задаче celery в другом экземпляре docker)
loop = asyncio.new_event_loop()
try:
asyncio.set_event_loop(loop)
loop.run_until_complete(asyncio.wait([_get_optimised_file(img, image_uri_map) for img in images_files]))
finally:
loop.close()
About _get_optimised_file()
не имеет никаких операций с базой данных, он проверяет, существует ли файл, если нет, он спит несколько секунд и проверяет снова.
Попробуйте понять, при каких обстоятельствах может возникнуть "асинхронный контекст", созданный и повторно используемый другим WSGI-запросом?