Opened 5 days ago

Closed 5 days ago

Last modified 3 days ago

#35757 closed Uncategorized (invalid)

memcached.PyMemcacheCache reentrancy problem with ASGI-based runserver

Reported by: Harm Verhagen Owned by:
Component: Uncategorized Version: 5.1
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Harm Verhagen)

I found that using memcached.PyMemcacheCache with runserver has reentrancy problems.

I implemented a json api in django, when issuing multiple request simultaneously from javascript, the requests sometimes crash.

I have urls that do something like this.

from django.core.cache import cache

def example_url(request):
    cache.incr("some key", 1)   # count requests

    return JsonResponse(data=data, status=201)

When issuing 5-10 requests simultaneously on a runserver, it often (not always) get this crash


  File "..../views/api.py", line 2512, in example_url
    cache.incr("some key", 1)
  File "./venv/lib/python3.12/site-packages/django/core/cache/backends/memcached.py", line 110, in incr
    val = self._cache.incr(key, delta)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "./venv/lib/python3.12/site-packages/pymemcache/client/hash.py", line 350, in incr
    return self._run_cmd("incr", key, False, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "./venv/lib/python3.12/site-packages/pymemcache/client/hash.py", line 322, in _run_cmd
    return self._safely_run_func(client, func, default_val, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "./venv/lib/python3.12/site-packages/pymemcache/client/hash.py", line 211, in _safely_run_func
    result = func(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^
  File "./venv/lib/python3.12/site-packages/pymemcache/client/base.py", line 827, in incr
    results = self._misc_cmd([cmd], b"incr", noreply)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "./venv/lib/python3.12/site-packages/pymemcache/client/base.py", line 1291, in _misc_cmd
    buf, line = _reader(self.sock, buf)
                ^^^^^^^^^^^^^^^^^^^^^^^
  File "./venv/lib/python3.12/site-packages/pymemcache/client/base.py", line 1658, in _readline
    buf = _recv(sock, RECV_SIZE)
          ^^^^^^^^^^^^^^^^^^^^^^
  File "./venv/lib/python3.12/site-packages/pymemcache/client/base.py", line 1750, in _recv
    return sock.recv(size)

 OSError: [Errno 9] Bad file descriptor

The following code looked a bit suspicious.

cache.backends.memcached.py:99 BaseMemcachedCache

    def close(self, **kwargs):
        # Many clients don't clean up connections properly.
        self._cache.disconnect_all()

Doesn't this race? closing a all connections, might close a connection of a parallel request that was just opened but not yet finished.

Notes

I'm not putting a heavy load on runserver, its a simple javascript app, that happens to do 10 requests on a single page. Django should be able to handle that, also in a dev environment

Workaround

Adding the --noasgi option seems to help.

./manage.py runserver --noasgi

Weird enough the (to me) more obvious, --nothreading option does _not_ help. That crashes in the same way.

NB: this workaround breaks django channels, so if you have websockets in your app, then this "workaround" is not usable.

Version info

Django==5.1.1
pymemcache==4.0.0
python 3.12.6

channels==3.0.5             <--- related?
channels-redis==3.4.1

Change History (11)

comment:1 by Harm Verhagen, 5 days ago

Description: modified (diff)

comment:2 by Harm Verhagen, 5 days ago

Description: modified (diff)

comment:3 by Harm Verhagen, 5 days ago

Description: modified (diff)

comment:4 by Harm Verhagen, 5 days ago

Summary: memcached.PyMemcacheCache reentrancy problem (OSError: [Errno 9] Bad file descriptor)memcached.PyMemcacheCache reentrancy problem with ASGI-based runserver

comment:5 by Harm Verhagen, 5 days ago

Description: modified (diff)

comment:6 by Harm Verhagen, 5 days ago

Description: modified (diff)

comment:7 by Harm Verhagen, 5 days ago

Description: modified (diff)

comment:8 by Harm Verhagen, 5 days ago

Its not just a reentrancy problem. Wrapping the calls to cache.incr() in a global mutex (threading.Lock(), just for testing purposes) does not seem to make any difference.

comment:9 by Harm Verhagen, 5 days ago

Description: modified (diff)

comment:10 by Sarah Boyce, 5 days ago

Resolution: invalid
Status: newclosed

This report seems better suited to be a support request. The best place to get answers to your issue is using any of the user support channels from this link.

Trac is for issues that are confirmed to be in Django. If, after debugging, you find out that this is indeed a bug in Django, please re-open with the specific details and please be sure to include a small Django project to reproduce or a failing test case.

Thank you!

comment:11 by Harm Verhagen, 3 days ago

Sarah Boyce wrote:

This report seems better suited to be a support request. The best place to get answers...

I actually intended to submit a bug report, not a support request or to get answers or help.

I'll try to cook up a small(er) test that demonstrates the problem.

Last edited 3 days ago by Harm Verhagen (previous) (diff)
Note: See TracTickets for help on using tickets.
Back to Top