Fixed #34806 -- Made cached_db session backend resilient to cache write errors.

Co-authored-by: Natalia <124304+nessita@users.noreply.github.com>
This commit is contained in:
Sulabh Katila 2024-02-21 19:51:58 -05:00 committed by GitHub
parent 6feaad9113
commit eceb5e2eea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 57 additions and 5 deletions

View File

@ -2,12 +2,16 @@
Cached, database-backed sessions.
"""
import logging
from django.conf import settings
from django.contrib.sessions.backends.db import SessionStore as DBStore
from django.core.cache import caches
KEY_PREFIX = "django.contrib.sessions.cached_db"
logger = logging.getLogger("django.contrib.sessions")
class SessionStore(DBStore):
"""
@ -52,7 +56,10 @@ class SessionStore(DBStore):
def save(self, must_create=False):
super().save(must_create)
self._cache.set(self.cache_key, self._session, self.get_expiry_age())
try:
self._cache.set(self.cache_key, self._session, self.get_expiry_age())
except Exception:
logger.exception("Error saving to cache (%s)", self._cache)
def delete(self, session_key=None):
super().delete(session_key)

View File

@ -286,6 +286,17 @@ Messages to this logger have ``params`` and ``sql`` in their extra context (but
unlike ``django.db.backends``, not duration). The values have the same meaning
as explained in :ref:`django-db-logger`.
.. _django-contrib-sessions-logger:
``django.contrib.sessions``
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Log messages related to the :doc:`session framework</topics/http/sessions>`.
* Non-fatal errors occurring when using the
:class:`django.contrib.sessions.backends.cached_db.SessionStore` engine are
logged as ``ERROR`` messages with the corresponding traceback.
Handlers
--------

View File

@ -115,7 +115,10 @@ Minor features
:mod:`django.contrib.sessions`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* ...
* :class:`django.contrib.sessions.backends.cached_db.SessionStore` now handles
exceptions when storing session information in the cache, logging proper
error messages with their traceback via the newly added
:ref:`sessions logger <django-contrib-sessions-logger>`.
:mod:`django.contrib.sitemaps`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -76,9 +76,17 @@ Once your cache is configured, you have to choose between a database-backed
cache or a non-persistent cache.
The cached database backend (``cached_db``) uses a write-through cache --
session writes are applied to both the cache and the database. Session reads
use the cache, or the database if the data has been evicted from the cache. To
use this backend, set :setting:`SESSION_ENGINE` to
session writes are applied to both the database and cache, in that order. If
writing to the cache fails, the exception is handled and logged via the
:ref:`sessions logger <django-contrib-sessions-logger>`, to avoid failing an
otherwise successful write operation.
.. versionchanged:: 5.1
Handling and logging of exceptions when writing to the cache was added.
Session reads use the cache, or the database if the data has been evicted from
the cache. To use this backend, set :setting:`SESSION_ENGINE` to
``"django.contrib.sessions.backends.cached_db"``, and follow the configuration
instructions for the `using database-backed sessions`_.

7
tests/cache/failing_cache.py vendored Normal file
View File

@ -0,0 +1,7 @@
from django.core.cache.backends.locmem import LocMemCache
class CacheClass(LocMemCache):
def set(self, *args, **kwargs):
raise Exception("Faked exception saving to cache")

View File

@ -517,6 +517,22 @@ class CacheDBSessionTests(SessionTestsMixin, TestCase):
with self.assertRaises(InvalidCacheBackendError):
self.backend()
@override_settings(
CACHES={"default": {"BACKEND": "cache.failing_cache.CacheClass"}}
)
def test_cache_set_failure_non_fatal(self):
"""Failing to write to the cache does not raise errors."""
session = self.backend()
session["key"] = "val"
with self.assertLogs("django.contrib.sessions", "ERROR") as cm:
session.save()
# A proper ERROR log message was recorded.
log = cm.records[-1]
self.assertEqual(log.message, f"Error saving to cache ({session._cache})")
self.assertEqual(str(log.exc_info[1]), "Faked exception saving to cache")
@override_settings(USE_TZ=True)
class CacheDBSessionWithTimeZoneTests(CacheDBSessionTests):