Author: jacob
Date: 2008-08-30 15:50:41 -0500 (Sat, 30 Aug 2008)
New Revision: 8750

Modified:
   django/trunk/AUTHORS
   django/trunk/django/contrib/sessions/backends/file.py
Log:
Fixed #8616 (again): prevent a race condition in the session file backend. Many 
thanks to Warren Smith for help and the eventual fix.

Modified: django/trunk/AUTHORS
===================================================================
--- django/trunk/AUTHORS        2008-08-30 20:17:58 UTC (rev 8749)
+++ django/trunk/AUTHORS        2008-08-30 20:50:41 UTC (rev 8750)
@@ -368,6 +368,7 @@
     Ben Slavin <[EMAIL PROTECTED]>
     sloonz <[EMAIL PROTECTED]>
     SmileyChris <[EMAIL PROTECTED]>
+    Warren Smith <[EMAIL PROTECTED]> 
     [EMAIL PROTECTED]
     Vsevolod Solovyov
     sopel

Modified: django/trunk/django/contrib/sessions/backends/file.py
===================================================================
--- django/trunk/django/contrib/sessions/backends/file.py       2008-08-30 
20:17:58 UTC (rev 8749)
+++ django/trunk/django/contrib/sessions/backends/file.py       2008-08-30 
20:50:41 UTC (rev 8750)
@@ -47,10 +47,14 @@
         try:
             session_file = open(self._key_to_file(), "rb")
             try:
-                try:
-                    session_data = self.decode(session_file.read())
-                except (EOFError, SuspiciousOperation):
-                    self.create()
+                file_data = session_file.read()
+                # Don't fail if there is no data in the session file.
+                # We may have opened the empty placeholder file.
+                if file_data:
+                    try:
+                        session_data = self.decode(file_data)
+                    except (EOFError, SuspiciousOperation):
+                        self.create()
             finally:
                 session_file.close()
         except IOError:
@@ -69,23 +73,59 @@
             return
 
     def save(self, must_create=False):
-        flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC | getattr(os, 
'O_BINARY', 0)
-        if must_create:
-            flags |= os.O_EXCL
-        # Because this may trigger a load from storage, we must do it before
-        # truncating the file to save.
+        # Get the session data now, before we start messing
+        # with the file it is stored within.
         session_data = self._get_session(no_load=must_create)
+
+        session_file_name = self._key_to_file()
+
         try:
-            fd = os.open(self._key_to_file(self.session_key), flags)
-            try:
-                os.write(fd, self.encode(session_data))
-            finally:
-                os.close(fd)
+            # Make sure the file exists.  If it does not already exist, an
+            # empty placeholder file is created.
+            flags = os.O_WRONLY | os.O_CREAT | getattr(os, 'O_BINARY', 0)
+            if must_create:
+                flags |= os.O_EXCL
+            fd = os.open(session_file_name, flags)
+            os.close(fd)
+
         except OSError, e:
             if must_create and e.errno == errno.EEXIST:
                 raise CreateError
             raise
-        except (IOError, EOFError):
+
+        # Write the session file without interfering with other threads
+        # or processes.  By writing to an atomically generated temporary
+        # file and then using the atomic os.rename() to make the complete
+        # file visible, we avoid having to lock the session file, while
+        # still maintaining its integrity.
+        #
+        # Note: Locking the session file was explored, but rejected in part
+        # because in order to be atomic and cross-platform, it required a
+        # long-lived lock file for each session, doubling the number of
+        # files in the session storage directory at any given time.  This
+        # rename solution is cleaner and avoids any additional overhead
+        # when reading the session data, which is the more common case
+        # unless SESSION_SAVE_EVERY_REQUEST = True.
+        #
+        # See ticket #8616.
+        dir, prefix = os.path.split(session_file_name)
+
+        try:
+            output_file_fd, output_file_name = tempfile.mkstemp(dir=dir,
+                prefix=prefix + '_out_')
+            try:
+                try:
+                    os.write(output_file_fd, self.encode(session_data))
+                finally:
+                    os.close(output_file_fd)
+                renamed = False
+                os.rename(output_file_name, session_file_name)
+                renamed = True
+            finally:
+                if not renamed:
+                    os.unlink(output_file_name)
+
+        except (OSError, IOError, EOFError):
             pass
 
     def exists(self, session_key):


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to