Hi everyone!
I’m coming here after opening ticket #34747 (Django hangs on async views with asycio.gather and an async ORM call) – Django and having it closed as #worksforme.
I have this simple view:
{{{
import asyncio
from django.http import HttpResponse
from myapp.models import MyModel
async def test_hang(request):
await asyncio.gather(MyModel.objects.acreate())
return HttpResponse(‘OK’)
}}}
that hangs when called from daphne or uvicorn. When called from the django shell (with ipdb) like await test_hang(None)
it works with no problem.
I created this minimal view to reproduce the issue, but in my project I’m creating a lot of tasks that have long external API requests saving the results to the db. Notice that with only one task it already hangs.
I haven’t found anything in the async documentation / channels / daphne stating that I cannot do something like this.
When setting a trace with ipdb I found that the line that gets stuck is:
{{{
/usr/local/Cellar/python@3.11/3.11.4_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/selectors.py:561
kev_list = self._selector.control(None, max_ev, timeout)
}}}
And when running with python -m ipdb and then ctrl c it gets stuck in this line:
{{{
File “/usr/local/Cellar/python@3.11/3.11.4_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/threading.py”, line 1132, in _wait_for_tstate_lock
if lock.acquire(block, timeout):
}}}
Tried with Python 3.11.4 in OS X and in docker (alpine).
Shouldn’t this simple view be working? If not, we should document it somehow. Any pointers much appreciated!
Can someone with OS X please try it? That might be the problem.
Thanks!
Some more info that I didn’t include in the ticket:
I’m using postgresql.
This is my pip freeze:
aiohttp==3.8.4
aiopg==1.3.5
aiosignal==1.3.1
anyio==3.7.1
appnope==0.1.3
asgiref==3.7.2
asttokens==2.2.1
astunparse==1.6.3
async-timeout==4.0.2
attrs==23.1.0
autobahn==23.6.2
Automat==22.10.0
backcall==0.2.0
boto3==1.28.3
botocore==1.31.3
certifi==2023.5.7
cffi==1.15.1
channels==4.0.0
channels-postgres @ git+https://github.com/khamaileon/channels_postgres.git@b9ad94e5f0925dc6f87e68834bbcd78ea6c9956c
channels-redis==4.1.0
charset-normalizer==3.1.0
click==8.1.3
constantly==15.1.0
cryptography==41.0.1
daphne==4.0.0
dataclasses-json==0.5.8
decorator==5.1.1
defusedxml==0.7.1
Django==4.2.2
django-allauth==0.54.0
django-environ==0.10.0
django-extensions==3.2.3
django-jsonform==2.17.2
django-s3-storage==0.14.0
djangorestframework==3.14.0
executing==1.2.0
frozenlist==1.3.3
greenlet==2.0.2
h11==0.14.0
httptools==0.5.0
hyperlink==21.0.0
idna==3.4
incremental==22.10.0
ipdb==0.13.13
ipython==8.14.0
jedi==0.18.2
jmespath==1.0.1
json-stream==2.3.2
json-stream-rs-tokenizer==0.4.21
langchain==0.0.227
langchainplus-sdk==0.0.20
lmql==0.0.6.5
MarkupSafe==2.1.3
marshmallow==3.19.0
marshmallow-enum==1.5.1
matplotlib-inline==0.1.6
msgpack==1.0.5
multidict==6.0.4
mypy-extensions==1.0.0
nodeenv==1.8.0
numexpr==2.8.4
numpy==1.25.0
oauthlib==3.2.2
openai==0.27.8
openapi-schema-pydantic==1.2.4
packaging==23.1
parso==0.8.3
pexpect==4.8.0
pickleshare==0.7.5
prompt-toolkit==3.0.38
psutil==5.9.5
psycopg2-binary==2.9.6
ptyprocess==0.7.0
pure-eval==0.2.2
pyasn1==0.5.0
pyasn1-modules==0.3.0
pycparser==2.21
pydantic==1.10.9
Pygments==2.15.1
PyJWT==2.7.0
pyOpenSSL==23.2.0
python-dateutil==2.8.2
python-dotenv==1.0.0
python3-openid==3.2.0
pytz==2023.3
PyYAML==6.0
redis==4.6.0
regex==2023.6.3
requests==2.31.0
requests-oauthlib==1.3.1
s3transfer==0.6.1
service-identity==23.1.0
six==1.16.0
sniffio==1.3.0
SQLAlchemy==2.0.17
sqlparse==0.4.4
stack-data==0.6.2
tenacity==8.2.2
termcolor==2.3.0
tiktoken==0.4.0
tqdm==4.65.0
traitlets==5.9.0
Twisted==22.10.0
txaio==23.1.1
typing-inspect==0.9.0
typing_extensions==4.6.3
urllib3==1.26.16
uvicorn==0.22.0
uvloop==0.17.0
watchfiles==0.19.0
wcwidth==0.2.6
websockets==11.0.3
Werkzeug==2.3.6
yarl==1.9.2
zope.interface==6.0
This are my settings:
import environ
import os
from pathlib import Path
env = environ.Env(
DEBUG=(bool, False)
)
environ.Env.read_env()
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = env('SECRET_KEY')
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': env('DATABASE_NAME'),
'USER': env('DATABASE_USER'),
'PASSWORD': env('DATABASE_PASSWORD'),
'HOST': env('DATABASE_HOST'),
'PORT': env('DATABASE_PORT'),
},
'channels_postgres': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': env('DATABASE_NAME'),
'USER': env('DATABASE_USER'),
'PASSWORD': env('DATABASE_PASSWORD'),
'HOST': env('DATABASE_HOST'),
'PORT': env('DATABASE_PORT'),
},
}
REDIS_LLM_MEMORY = f"{ env('REDIS_URL') }0"
CHANNEL_LAYERS = {
"default": {
'BACKEND': 'channels_postgres.core.PostgresChannelLayer',
'CONFIG': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': env('DATABASE_NAME'),
'USER': env('DATABASE_USER'),
'PASSWORD': env('DATABASE_PASSWORD'),
'HOST': env('DATABASE_HOST'),
'PORT': env('DATABASE_PORT'),
},
},
}
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')
CSRF_TRUSTED_ORIGINS = env.list('CSRF_TRUSTED_ORIGINS')
# Application definition
INSTALLED_APPS = [
'daphne',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.sites',
'rest_framework',
'django_jsonform',
'channels_postgres',
'accounts',
'evaluation',
'interview',
'utils',
'front',
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.google',
]
MIDDLEWARE = [
'utils.middleware.HealthCheckMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
SITE_ID = 1
ROOT_URLCONF = 'myproject.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
# custom form renderer
from django.forms.renderers import TemplatesSetting
class CustomFormRenderer(TemplatesSetting):
form_template_name = "forms/form_snippet.html"
FORM_RENDERER = "angelai.settings.CustomFormRenderer"
WSGI_APPLICATION = 'myproject.wsgi.application'
ASGI_APPLICATION = 'myproject.asgi.application'
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
AUTH_USER_MODEL = "accounts.User"
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
]
ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_CONFIRM_EMAIL_ON_GET = True
ACCOUNT_EMAIL_VERIFICATION = 'none'
ACCOUNT_DEFAULT_HTTP_PROTOCOL = 'http'
ACCOUNT_MAX_EMAIL_ADDRESSES = 1
ACCOUNT_LOGIN_ATTEMPTS_LIMIT = 15
ACCOUNT_EMAIL_CONFIRMATION_ANONYMOUS_REDIRECT_URL = True
ACCOUNT_LOGOUT_ON_GET = True
ACCOUNT_LOGIN_ON_PASSWORD_RESET = True
ACCOUNT_PRESERVE_USERNAME_CASING = False
ACCOUNT_SESSION_REMEMBER = True
ACCOUNT_USERNAME_REQUIRED = False
SOCIALACCOUNT_AUTO_SIGNUP = True
SOCIALACCOUNT_LOGIN_ON_GET = True
# Provider specific settings
SOCIALACCOUNT_PROVIDERS = {
'google': {
# For each OAuth based provider, either add a ``SocialApp``
# (``socialaccount`` app) containing the required client
# credentials, or list them here:
'APP': {
'client_id': env('GOOGLE_OAUTH_CLIENT_ID'),
'secret': env('GOOGLE_OAUTH_CLIENT_SECRET'),
'key': ''
}
}
}
from django.urls import reverse_lazy
LOGIN_REDIRECT_URL = reverse_lazy('start')
# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
STATIC_URL = 'static/'
STATIC_ROOT = BASE_DIR / '../collected_static'
STATICFILES_DIRS = [
("frontend", BASE_DIR / "../frontend/dist"),
]
ALLOW_FRONTEND_DEV = False
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
OPENAI_API_KEY = env('OPENAI_API_KEY')
GOOGLE_ANALYTICS_ID=env('GOOGLE_ANALYTICS_ID')
EVALUATION_PITCHES_DIR = "../another"
EVALUATION_SCORE_TOLERANCE = 2
OPENAI_OPTIONS = {
'model': 'gpt-3.5-turbo-16k',
'temperature': .8,
'verbose': True,
}
USE_TLS = False
HTTP_PROTOCOL = 'http'
WEBSOCKET_PROTOCOL = 'ws'
try:
from myproject.settings_local import *
INSTALLED_APPS.extend(EXTRA_INSTALLED_APPS)
except ImportError:
# using print and not log here as logging is yet not configured
print('local settings not found')
pass
And my settings_local.py
BASE_DIR = Path(__file__).resolve().parent.parent
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
SITE_ID = 1
EXTRA_INSTALLED_APPS = (
'django_extensions',
)
ALLOWED_HOSTS = ['*']