Skip to content

Commit

Permalink
Fixed #34806 -- Made cached_db session backend resilient to cache wri…
Browse files Browse the repository at this point in the history
…te errors.

Co-authored-by: Natalia <[email protected]>
  • Loading branch information
sulabhkatila and nessita authored Feb 22, 2024
1 parent 6feaad9 commit eceb5e2
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 5 deletions.
9 changes: 8 additions & 1 deletion django/contrib/sessions/backends/cached_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down Expand Up @@ -52,7 +56,10 @@ def exists(self, session_key):

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)
Expand Down
11 changes: 11 additions & 0 deletions docs/ref/logging.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
--------

Expand Down
5 changes: 4 additions & 1 deletion docs/releases/5.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
14 changes: 11 additions & 3 deletions docs/topics/http/sessions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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`_.

Expand Down
7 changes: 7 additions & 0 deletions tests/cache/failing_cache.py
Original file line number Diff line number Diff line change
@@ -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")
16 changes: 16 additions & 0 deletions tests/sessions_tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,22 @@ def test_non_default_cache(self):
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):
Expand Down

0 comments on commit eceb5e2

Please sign in to comment.