mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	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:
		| @@ -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) | ||||
|   | ||||
| @@ -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 | ||||
| -------- | ||||
|  | ||||
|   | ||||
| @@ -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` | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
|   | ||||
| @@ -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
									
								
							
							
						
						
									
										7
									
								
								tests/cache/failing_cache.py
									
									
									
									
										vendored
									
									
										Normal 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") | ||||
| @@ -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): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user