From 02f86a1c7c240b80c486269e3b16131a7b44d637 Mon Sep 17 00:00:00 2001 From: Malcolm Tredinnick Date: Fri, 29 Aug 2008 01:44:11 +0000 Subject: [PATCH] Fixed #8616 -- Fixed a race condition in the file-based session backend. Thanks to warren@wandrsmith.net for the patch. git-svn-id: http://code.djangoproject.com/svn/django/trunk@8688 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 1 + django/contrib/sessions/backends/file.py | 48 ++++++++++++++++++++---- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/AUTHORS b/AUTHORS index 2428f6a9ac..4b753f59a4 100644 --- a/AUTHORS +++ b/AUTHORS @@ -363,6 +363,7 @@ answer newbie questions, and generally made Django that much better: Ben Slavin sloonz SmileyChris + Warren Smith smurf@smurf.noris.de Vsevolod Solovyov sopel diff --git a/django/contrib/sessions/backends/file.py b/django/contrib/sessions/backends/file.py index 36a03b6353..0391e09f25 100644 --- a/django/contrib/sessions/backends/file.py +++ b/django/contrib/sessions/backends/file.py @@ -5,7 +5,9 @@ import tempfile from django.conf import settings from django.contrib.sessions.backends.base import SessionBase, CreateError from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured +from django.core.files import locks +IO_LOCK_SUFFIX = "_iolock" class SessionStore(SessionBase): """ @@ -42,17 +44,35 @@ class SessionStore(SessionBase): return os.path.join(self.storage_path, self.file_prefix + session_key) + def _key_to_io_lock_file(self, session_key=None): + """ + Get the I/O lock file associated with this session key. + """ + return self._key_to_file(session_key) + IO_LOCK_SUFFIX + def load(self): session_data = {} try: - session_file = open(self._key_to_file(), "rb") + # Open and acquire a shared lock on the I/O lock file before + # attempting to read the session file. This makes us wait to read + # the session file until another thread or process is finished + # writing it. + lock_path = self._key_to_io_lock_file() + io_lock_file = open(lock_path, "rb") + locks.lock(io_lock_file, locks.LOCK_SH) try: + session_file = open(self._key_to_file(), "rb") try: - session_data = self.decode(session_file.read()) - except (EOFError, SuspiciousOperation): - self.create() + try: + session_data = self.decode(session_file.read()) + except (EOFError, SuspiciousOperation): + self.create() + finally: + session_file.close() finally: - session_file.close() + locks.unlock(io_lock_file) + io_lock_file.close() + os.unlink(lock_path) except IOError: pass return session_data @@ -76,11 +96,23 @@ class SessionStore(SessionBase): # truncating the file to save. session_data = self._get_session(no_load=must_create) try: - fd = os.open(self._key_to_file(self.session_key), flags) + # Open and acquire an exclusive lock on the I/O lock file before + # attempting to write the session file. This makes other threads + # or processes wait to read or write the session file until we are + # finished writing it. + lock_path = self._key_to_io_lock_file() + io_lock_file = open(lock_path, "wb") + locks.lock(io_lock_file, locks.LOCK_EX) try: - os.write(fd, self.encode(session_data)) + fd = os.open(self._key_to_file(self.session_key), flags) + try: + os.write(fd, self.encode(session_data)) + finally: + os.close(fd) finally: - os.close(fd) + locks.unlock(io_lock_file) + io_lock_file.close() + os.unlink(lock_path) except OSError, e: if must_create and e.errno == errno.EEXIST: raise CreateError