Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #8616 (again): prevent a race condition in the session file bac…

…kend. Many thanks to Warren Smith for help and the eventual fix.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@8750 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit eebc7caa6380096f5a8402db9a6e4a324585551b 1 parent 1729d92
Jacob Kaplan-Moss authored August 30, 2008
1  AUTHORS
@@ -368,6 +368,7 @@ answer newbie questions, and generally made Django that much better:
368 368
     Ben Slavin <benjamin.slavin@gmail.com>
369 369
     sloonz <simon.lipp@insa-lyon.fr>
370 370
     SmileyChris <smileychris@gmail.com>
  371
+    Warren Smith <warren@wandrsmith.net> 
371 372
     smurf@smurf.noris.de
372 373
     Vsevolod Solovyov
373 374
     sopel
70  django/contrib/sessions/backends/file.py
@@ -47,10 +47,14 @@ def load(self):
47 47
         try:
48 48
             session_file = open(self._key_to_file(), "rb")
49 49
             try:
50  
-                try:
51  
-                    session_data = self.decode(session_file.read())
52  
-                except (EOFError, SuspiciousOperation):
53  
-                    self.create()
  50
+                file_data = session_file.read()
  51
+                # Don't fail if there is no data in the session file.
  52
+                # We may have opened the empty placeholder file.
  53
+                if file_data:
  54
+                    try:
  55
+                        session_data = self.decode(file_data)
  56
+                    except (EOFError, SuspiciousOperation):
  57
+                        self.create()
54 58
             finally:
55 59
                 session_file.close()
56 60
         except IOError:
@@ -69,23 +73,59 @@ def create(self):
69 73
             return
70 74
 
71 75
     def save(self, must_create=False):
72  
-        flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC | getattr(os, 'O_BINARY', 0)
73  
-        if must_create:
74  
-            flags |= os.O_EXCL
75  
-        # Because this may trigger a load from storage, we must do it before
76  
-        # truncating the file to save.
  76
+        # Get the session data now, before we start messing
  77
+        # with the file it is stored within.
77 78
         session_data = self._get_session(no_load=must_create)
  79
+
  80
+        session_file_name = self._key_to_file()
  81
+
78 82
         try:
79  
-            fd = os.open(self._key_to_file(self.session_key), flags)
80  
-            try:
81  
-                os.write(fd, self.encode(session_data))
82  
-            finally:
83  
-                os.close(fd)
  83
+            # Make sure the file exists.  If it does not already exist, an
  84
+            # empty placeholder file is created.
  85
+            flags = os.O_WRONLY | os.O_CREAT | getattr(os, 'O_BINARY', 0)
  86
+            if must_create:
  87
+                flags |= os.O_EXCL
  88
+            fd = os.open(session_file_name, flags)
  89
+            os.close(fd)
  90
+
84 91
         except OSError, e:
85 92
             if must_create and e.errno == errno.EEXIST:
86 93
                 raise CreateError
87 94
             raise
88  
-        except (IOError, EOFError):
  95
+
  96
+        # Write the session file without interfering with other threads
  97
+        # or processes.  By writing to an atomically generated temporary
  98
+        # file and then using the atomic os.rename() to make the complete
  99
+        # file visible, we avoid having to lock the session file, while
  100
+        # still maintaining its integrity.
  101
+        #
  102
+        # Note: Locking the session file was explored, but rejected in part
  103
+        # because in order to be atomic and cross-platform, it required a
  104
+        # long-lived lock file for each session, doubling the number of
  105
+        # files in the session storage directory at any given time.  This
  106
+        # rename solution is cleaner and avoids any additional overhead
  107
+        # when reading the session data, which is the more common case
  108
+        # unless SESSION_SAVE_EVERY_REQUEST = True.
  109
+        #
  110
+        # See ticket #8616.
  111
+        dir, prefix = os.path.split(session_file_name)
  112
+
  113
+        try:
  114
+            output_file_fd, output_file_name = tempfile.mkstemp(dir=dir,
  115
+                prefix=prefix + '_out_')
  116
+            try:
  117
+                try:
  118
+                    os.write(output_file_fd, self.encode(session_data))
  119
+                finally:
  120
+                    os.close(output_file_fd)
  121
+                renamed = False
  122
+                os.rename(output_file_name, session_file_name)
  123
+                renamed = True
  124
+            finally:
  125
+                if not renamed:
  126
+                    os.unlink(output_file_name)
  127
+
  128
+        except (OSError, IOError, EOFError):
89 129
             pass
90 130
 
91 131
     def exists(self, session_key):

0 notes on commit eebc7ca

Please sign in to comment.
Something went wrong with that request. Please try again.