Here is the full error: The request's session was deleted before the request completed. The user may have logged out in a concurrent request, for example.
I am using python-memcached with my sessions using my cache. Every few days I get one of these errors. Its thrown by an UpdateError on request.session.save(). It comes from line 60 in sessions/middleware.py. 99% of the time everything works normally. I have seen this error at many different URLs for GET and POST requests. Users report that they are not clicking the logout button. They are also reporting that this happens 5 minutes after logging in, so their sessions are not expiring. I have 0 evictions on my cache for over a month it has been running. If I Google this error, it looks like no one has ever gotten it before.
I think the connections to memcached might be closing for some reason. Its running on localhost. The only other time I saw this error is when I set my cache config to a server that had memcached running but it was not listening on that interface. That would generate this exact exception on every request. So is there some way that memcache is refusing to listen for a second or two or dropping connections?
Here are my settings:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211',
'TIMEOUT': 1209600, # Two weeks
},
}
SESSION_SAVE_EVERY_REQUEST = True
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_COOKIE_SECURE = CSRF_COOKIE_SECURE = True
SESSION_COOKIE_AGE = 60 * 90 # In 90 minutes
It seems the sure way to cause this error is to run cache.delete with the session key in a shell while the request is running. So something is deleting cache keys. I don't know if its Django or Memcached. Memcached does say STAT evictions 0
.
I made this middleware to solve the issue. It seems to have taken care of it. Also check your file descriptor limits.
class SLSessionMiddleware(SessionMiddleware):
"""
Fixes a bug where sessions sometime fail to be set. Catches the error 10 times and gives up.
"""
def process_response(self, request, response):
last_exception = None
for i in range(10):
try:
return super().process_response(request, response)
except Exception as e:
request.session.cycle_key()
time.sleep(1)
last_exception = e
raise last_exception