summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSulabh Katila <sulabhkatila@gmail.com>2024-02-21 19:51:58 -0500
committerGitHub <noreply@github.com>2024-02-21 21:51:58 -0300
commiteceb5e2eea554ea0f9baf7abc1b1972459854cef (patch)
tree5327cc03f0462e3c2c3feeab939bf698534f95a9
parent6feaad9113fd38ba3970032d2b7856c77403e29e (diff)
Fixed #34806 -- Made cached_db session backend resilient to cache write errors.
Co-authored-by: Natalia <124304+nessita@users.noreply.github.com>
-rw-r--r--django/contrib/sessions/backends/cached_db.py9
-rw-r--r--docs/ref/logging.txt11
-rw-r--r--docs/releases/5.1.txt5
-rw-r--r--docs/topics/http/sessions.txt14
-rw-r--r--tests/cache/failing_cache.py7
-rw-r--r--tests/sessions_tests/tests.py16
6 files changed, 57 insertions, 5 deletions
diff --git a/django/contrib/sessions/backends/cached_db.py b/django/contrib/sessions/backends/cached_db.py
index 3125a71cd0..a2a8cf47af 100644
--- a/django/contrib/sessions/backends/cached_db.py
+++ b/django/contrib/sessions/backends/cached_db.py
@@ -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)
diff --git a/docs/ref/logging.txt b/docs/ref/logging.txt
index 6d8861299f..8a7e58997e 100644
--- a/docs/ref/logging.txt
+++ b/docs/ref/logging.txt
@@ -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
--------
diff --git a/docs/releases/5.1.txt b/docs/releases/5.1.txt
index 3fe0e65410..a4a7f359c6 100644
--- a/docs/releases/5.1.txt
+++ b/docs/releases/5.1.txt
@@ -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`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt
index 4f635f1704..d799c245de 100644
--- a/docs/topics/http/sessions.txt
+++ b/docs/topics/http/sessions.txt
@@ -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`_.
diff --git a/tests/cache/failing_cache.py b/tests/cache/failing_cache.py
new file mode 100644
index 0000000000..e2f0043bb7
--- /dev/null
+++ b/tests/cache/failing_cache.py
@@ -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")
diff --git a/tests/sessions_tests/tests.py b/tests/sessions_tests/tests.py
index 7e0677d08d..c8c556b4dc 100644
--- a/tests/sessions_tests/tests.py
+++ b/tests/sessions_tests/tests.py
@@ -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):