#33533: Should it be impossible for a session dict to return more than one value for a key? ----------------------------------+-------------------------------------- Reporter: Michael | Owner: nobody Type: Uncategorized | Status: closed Component: contrib.sessions | Version: 4.0 Severity: Normal | Resolution: invalid Keywords: | Triage Stage: Unreviewed Has patch: 0 | Needs documentation: 0 Needs tests: 0 | Patch needs improvement: 0 Easy pickings: 0 | UI/UX: 0 ----------------------------------+--------------------------------------
Old description: > In my project, I store the serial number of the device that logs in it's > session. However in the code that gets the value from the session: > > {{{ > SESSION_SERIAL_NUMBER = 'terminal_serial_number' > > user._serial_number = request.session.get(SESSION_SERIAL_NUMBER, '') > }}} > > Very occasionally throws an error. The part that baffles me is: > > > get() returned more than one Session -- it returned more than 20! > > How can a dict return more than one result, surely that is a bug for a > dict to return more than one value? > > Note: When I query the database `django_sessions` table with the given > `sessionid`, it only returns one row. > > Heres the error (from the email): > > {{{ > Internal Server Error: /accounts/login/ > > ProgrammingError at /accounts/login/ > no results to fetch > }}} > > And the traceback: > > {{{ > Traceback (most recent call last): > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/contrib/sessions/backends/base.py", line 180, in > _get_session > return self._session_cache > > During handling of the above exception ('SessionStore' object has no > attribute '_session_cache'), another exception occurred: > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/core/handlers/exception.py", line 47, in inner > response = get_response(request) > File "./dist/terminals/middleware.py", line 11, in __call__ > user._serial_number = request.session.get(SESSION_SERIAL_NUMBER, '') > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/contrib/sessions/backends/base.py", line 65, in get > return self._session.get(key, default) > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/contrib/sessions/backends/base.py", line 185, in > _get_session > self._session_cache = self.load() > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/contrib/sessions/backends/db.py", line 43, in load > s = self._get_session_from_db() > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/contrib/sessions/backends/db.py", line 32, in > _get_session_from_db > return self.model.objects.get( > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/db/models/manager.py", line 85, in manager_method > return getattr(self.get_queryset(), name)(*args, **kwargs) > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/db/models/query.py", line 443, in get > raise self.model.MultipleObjectsReturned( > > During handling of the above exception (get() returned more than one > Session -- it returned more than 20!), another exception occurred: > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/contrib/sessions/backends/base.py", line 180, in > _get_session > return self._session_cache > > During handling of the above exception ('SessionStore' object has no > attribute '_session_cache'), another exception occurred: > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/db/utils.py", line 97, in inner > return func(*args, **kwargs) > > The above exception (no results to fetch) was the direct cause of the > following exception: > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/core/handlers/exception.py", line 47, in inner > response = get_response(request) > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/utils/deprecation.py", line 126, in __call__ > response = response or self.get_response(request) > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/core/handlers/exception.py", line 49, in inner > response = response_for_exception(request, exc) > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/core/handlers/exception.py", line 110, in > response_for_exception > response = handle_uncaught_exception(request, > get_resolver(get_urlconf()), sys.exc_info()) > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/core/handlers/exception.py", line 149, in > handle_uncaught_exception > return callback(request) > File "./core/base/views.py", line 152, in error_500_view > response = render(request, "core.base/500.html", context=context) > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/shortcuts.py", line 19, in render > content = loader.render_to_string(template_name, context, request, > using=using) > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/template/loader.py", line 62, in render_to_string > return template.render(context, request) > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/template/backends/django.py", line 61, in render > return self.template.render(context) > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/template/base.py", line 174, in render > with context.bind_template(self): > File "/usr/lib/python3.8/contextlib.py", line 113, in __enter__ > return next(self.gen) > File "/home/company/.venv/project/lib/python3.8/site- > packages/debug_toolbar/panels/templates/panel.py", line 46, in > _request_context_bind_template > context = processor(self.request) > File "./core/base/context_processors.py", line 13, in project > menu = get_plug_menu(request.user) > File "./dist/plug/menu.py", line 58, in get_plug_menu > if user.is_anonymous or user.is_terminal: > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/utils/functional.py", line 248, in inner > self._setup() > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/utils/functional.py", line 384, in _setup > self._wrapped = self._setupfunc() > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/contrib/auth/middleware.py", line 25, in <lambda> > request.user = SimpleLazyObject(lambda: get_user(request)) > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/contrib/auth/middleware.py", line 11, in get_user > request._cached_user = auth.get_user(request) > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/contrib/auth/__init__.py", line 177, in get_user > user_id = _get_user_session_key(request) > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/contrib/auth/__init__.py", line 60, in > _get_user_session_key > return > get_user_model()._meta.pk.to_python(request.session[SESSION_KEY]) > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/contrib/sessions/backends/base.py", line 50, in > __getitem__ > return self._session[key] > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/contrib/sessions/backends/base.py", line 185, in > _get_session > self._session_cache = self.load() > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/contrib/sessions/backends/db.py", line 43, in load > s = self._get_session_from_db() > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/contrib/sessions/backends/db.py", line 32, in > _get_session_from_db > return self.model.objects.get( > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/db/models/manager.py", line 85, in manager_method > return getattr(self.get_queryset(), name)(*args, **kwargs) > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/db/models/query.py", line 435, in get > num = len(clone) > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/db/models/query.py", line 262, in __len__ > self._fetch_all() > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/db/models/query.py", line 1354, in _fetch_all > self._result_cache = list(self._iterable_class(self)) > File "/home/company/.venv/project/lib/python3.8/site- > 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 "/home/company/.venv/project/lib/python3.8/site- > packages/django/db/models/sql/compiler.py", line 1234, in execute_sql > return list(result) > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/db/models/sql/compiler.py", line 1678, in cursor_iter > for rows in iter((lambda: cursor.fetchmany(itersize)), sentinel): > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/db/models/sql/compiler.py", line 1678, in <lambda> > for rows in iter((lambda: cursor.fetchmany(itersize)), sentinel): > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/db/utils.py", line 97, in inner > return func(*args, **kwargs) > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/db/utils.py", line 90, in __exit__ > raise dj_exc_value.with_traceback(traceback) from exc_value > File "/home/company/.venv/project/lib/python3.8/site- > packages/django/db/utils.py", line 97, in inner > return func(*args, **kwargs) > > Exception Type: ProgrammingError at /accounts/login/ > Exception Value: no results to fetch > Request information: > USER: AnonymousUser > }}} > > This is the middleware it sits in: > {{{ > from dist.terminals.apps import SESSION_SERIAL_NUMBER > > class UserSerialNumberMiddleware: > def __init__(self, get_response): > self.get_response = get_response > # One-time configuration and initialization. > > def __call__(self, request): > user = request.user > user._serial_number = request.session.get(SESSION_SERIAL_NUMBER, > '') > response = self.get_response(request) > return response > }}} New description: In my project, there are some methods on a custom user model that require the `request` to calculate certain values. This simple middleware does the trick: {{{ class AttachRequestToUserMiddleware: """Adds the request to the user object, so session information can be looked up by the custom user model. Must come after django.contrib.auth.middleware.AuthenticationMiddleware which adds the user""" def __init__(self, get_response): self.get_response = get_response def __call__(self, request): request.user.request = request return self.get_response(request) }}} In production, when there are multiple workers running parrallel by uWSGI, if one has `SESSION_SAVE_EVERY_REQUEST = True`, then if one makes async requests from JavaScript (e.g. say a Service Worker caching pages on install), then the way it saves/retrieves sessions on every request fails in many spectacular ways. Here are some example trace backs: {{{ Django Version: 4.0.1 Python Version: 3.8.5 Exception Type: ProgrammingError Exception Value: no results to fetch Exception Location: /home/michael/.venv/project/lib/python3.8/site- packages/django/db/utils.py, line 97, in inner /home/michael/.venv/project/lib/python3.8/site- packages/django/db/utils.py, line 97, in inner return func(*args, **kwargs) Exception Type: RuntimeError Exception Value: generator raised StopIteration /home/michael/.venv/project/lib/python3.8/site- packages/django/contrib/auth/__init__.py, line 60, in _get_user_session_key return get_user_model()._meta.pk.to_python(request.session[SESSION_KEY]) Exception Type: MultipleObjectsReturned Exception Value: get() returned more than one Session -- it returned 2! /home/michael/.venv/project/lib/python3.8/site- packages/django/contrib/sessions/backends/base.py, line 180, in _get_session return self._session_cache /home/michael/.venv/project/lib/python3.8/site- packages/django/core/handlers/exception.py, line 47, in inner response = get_response(request) ./core/accounts/middleware.py, line 33, in __call__ request.user.request = request /home/michael/.venv/project/lib/python3.8/site- packages/django/utils/functional.py, line 278, in __setattr__ self._setup() /home/michael/.venv/project/lib/python3.8/site- packages/django/utils/functional.py, line 384, in _setup self._wrapped = self._setupfunc() /home/michael/.venv/project/lib/python3.8/site- packages/django/contrib/auth/middleware.py, line 25, in <lambda> request.user = SimpleLazyObject(lambda: get_user(request)) /home/michael/.venv/project/lib/python3.8/site- packages/django/contrib/auth/middleware.py, line 11, in get_user request._cached_user = auth.get_user(request) /home/michael/.venv/project/lib/python3.8/site- packages/django/contrib/auth/__init__.py, line 177, in get_user user_id = _get_user_session_key(request) /home/michael/.venv/project/lib/python3.8/site- packages/django/contrib/auth/__init__.py, line 60, in _get_user_session_key return get_user_model()._meta.pk.to_python(request.session[SESSION_KEY]) /home/michael/.venv/project/lib/python3.8/site- packages/django/contrib/sessions/backends/base.py, line 50, in __getitem__ return self._session[key] /home/michael/.venv/project/lib/python3.8/site- packages/django/contrib/sessions/backends/base.py, line 185, in _get_session self._session_cache = self.load() /home/michael/.venv/project/lib/python3.8/site- packages/django/contrib/sessions/backends/db.py, line 43, in load s = self._get_session_from_db() /home/michael/.venv/project/lib/python3.8/site- packages/django/contrib/sessions/backends/db.py, line 32, in _get_session_from_db return self.model.objects.get( /home/michael/.venv/project/lib/python3.8/site- packages/django/db/models/manager.py, line 85, in manager_method return getattr(self.get_queryset(), name)(*args, **kwargs) /home/michael/.venv/project/lib/python3.8/site- packages/django/db/models/query.py, line 443, in get raise self.model.MultipleObjectsReturned( }}} -- Comment (by Michael): Replying to [comment:1 Tim Graham]: > See TicketClosingReasons/UseSupportChannels for ways to get help if you cannot debug the issue yourself. Please only use this ticket tracker for reporting confirmed bugs. Thanks! Hi, I have done some more digging with some help: https://forum.djangoproject.com/t/should-it-be-impossible-for-a-session- dict-to-return-more-than-one-value-for-a-key/12298/13 I think the issue is this: if it receives multiple requests at the same time (I have a service worker that requests many pages at installation so it can cache them), and if `SESSION_SAVE_EVERY_REQUEST = True`, then the way it handles session updates it just falls over in many spectacular ways. My for now my fix is to set SESSION_COOKIE_AGE to over a year, and have `SESSION_SAVE_EVERY_REQUEST = False` -- Ticket URL: <https://code.djangoproject.com/ticket/33533#comment:3> Django <https://code.djangoproject.com/> The Web framework for perfectionists with deadlines. -- You received this message because you are subscribed to the Google Groups "Django updates" group. To unsubscribe from this group and stop receiving emails from it, send an email to django-updates+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/django-updates/0107017f8f4b6d1d-af60c0a5-ba9b-42c6-a05d-eaf50fb98df0-000000%40eu-central-1.amazonses.com.