-
Notifications
You must be signed in to change notification settings - Fork 271
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
memcached.PyMemcacheCache reentrancy problem with ASGI-based runserver #534
Comments
Thanks for the follow up @harmv. Very clear. 👍 |
From what I can see , pymemcache implement a custom threading pool ... https://github.com/pinterest/pymemcache/blob/master/pymemcache/pool.py For Twisted based application, any code that is expected to run in a thread should use the "official" Twisted threading api see the docs here https://docs.twisted.org/en/stable/core/howto/threading.html From what I can see, the code from As a general rule, you can't mix Twisted code with random threading code. Twisted exists to avoid the need for using threads and the majority of Twisted code is not thread safe. I think that the fix here, is to implement a Twisted aware memcache backend |
Thanks for the inout @adiroiban! That makes sense. |
@adiroiban , I doubt that that is the problem. I actually can't find a threadpool in pymemcache. When I look in https://github.com/pinterest/pymemcache/blob/master/pymemcache/pool.py (or in any code of pymemcache) I don't see it using threads at all. I see
or am I missing something? |
I am not familiar with how the django server Is each request execute inside the Twisted loop or inside Django's own threads ? I see this, which hint that the pymemcache is executed from a thread.
|
This issue has been ongoing for a couple of months now: any concurrent requests cause the server to crash. Although removing Daphne temporarily solved the problem during development.
|
I can confirm this is an issue with the I was able to resolve the issue using the ThreadMappedPool feature of from django.core.cache.backends.memcached import PyLibMCCache
from functools import cached_property
from pylibmc.pools import ThreadMappedPool
class DeferredLookup:
def __init__(self, pool):
self._pool = pool
def __getattr__(self, name):
def deferred(*args, **kwargs):
with self._pool.reserve() as conn:
return getattr(conn, name)(*args, **kwargs)
return deferred
class ThreadedPyLibMCCache(PyLibMCCache):
"""
Slight extension to the pylibmc cache for Django which uses the
ThreadMappedPool feature of pylibmc to ensure that each thread has a
corresponding connection to the memcached server. This provides both a
performance benefit that no thread needs to wait for a connection to be
available and also a safety benefit that the connection protocol and data is
not corrupted from thread preemption.
References:
https://sendapatch.se/projects/pylibmc/pooling.html#thread-mapped-pooling
"""
@cached_property
def _cache(self):
# Return an object which, when an attribute is looked up (eg. such as
# `.get`), returns a callable closure. Then, when the closure is called,
# acquire a connection from the pool and use it to perform the cache
# operation
pool = ThreadMappedPool(super()._cache)
return DeferredLookup(pool) And then use it for the Django CACHES CACHES = {
'default': {
'BACKEND': 'path.to.ThreadedPyLibMCCache',
'LOCATION': '127.0.0.1:11211',
'OPTIONS': {
'binary': True
}
}
} |
As requested (Carlton Gibson) in Django ticket https://code.djangoproject.com/ticket/35757#comment:21 I open an issue in this repo.
Problem
When using 'daphne' + 'channels' in a Django polls project, then runserver has reentrancy issues, which becomes visible when enabling for example memcached.PyMemcacheCache.
Steps to reproduce
run a memcached instance:
memcached -vv
Fire up runserver
./manage.py runserver
Browse to http://localhost:8000/polls/ and see everything working. An incremeting counter shows.
Now to reproduce the bug do many requests simultaneously, by running a script get_parallel.sh
Result
within a few seconds exceptions occur like
After a few runs runserver stops serving requests completely, all requests, even sequential (slow ones) ones from a browser, get a 500 with this traceback.
Versions
channels==4.1.0
daphne==4.1.2
Django==5.1.1
python 3.12.6
Reproducibility
The problem appears within seconds when running the .sh script.
I'm also able to reproduce the problem with channels 3
The problem only occurs with runserver, on production we use
daphne <ourappname>.asgi:application -b 0.0.0.0 -p 8000
and that seems to work fine.When removing
daphne
from INSTALLED_APPS, the problem disappears. (but then ofcourse channels are not working properly)The text was updated successfully, but these errors were encountered: