Fixed #8616 (again): prevent a race condition in the session file backend. 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
This commit is contained in:
parent
1729d92f71
commit
eebc7caa63
1
AUTHORS
1
AUTHORS
|
@ -368,6 +368,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
Ben Slavin <benjamin.slavin@gmail.com>
|
Ben Slavin <benjamin.slavin@gmail.com>
|
||||||
sloonz <simon.lipp@insa-lyon.fr>
|
sloonz <simon.lipp@insa-lyon.fr>
|
||||||
SmileyChris <smileychris@gmail.com>
|
SmileyChris <smileychris@gmail.com>
|
||||||
|
Warren Smith <warren@wandrsmith.net>
|
||||||
smurf@smurf.noris.de
|
smurf@smurf.noris.de
|
||||||
Vsevolod Solovyov
|
Vsevolod Solovyov
|
||||||
sopel
|
sopel
|
||||||
|
|
|
@ -47,10 +47,14 @@ class SessionStore(SessionBase):
|
||||||
try:
|
try:
|
||||||
session_file = open(self._key_to_file(), "rb")
|
session_file = open(self._key_to_file(), "rb")
|
||||||
try:
|
try:
|
||||||
try:
|
file_data = session_file.read()
|
||||||
session_data = self.decode(session_file.read())
|
# Don't fail if there is no data in the session file.
|
||||||
except (EOFError, SuspiciousOperation):
|
# We may have opened the empty placeholder file.
|
||||||
self.create()
|
if file_data:
|
||||||
|
try:
|
||||||
|
session_data = self.decode(file_data)
|
||||||
|
except (EOFError, SuspiciousOperation):
|
||||||
|
self.create()
|
||||||
finally:
|
finally:
|
||||||
session_file.close()
|
session_file.close()
|
||||||
except IOError:
|
except IOError:
|
||||||
|
@ -69,23 +73,59 @@ class SessionStore(SessionBase):
|
||||||
return
|
return
|
||||||
|
|
||||||
def save(self, must_create=False):
|
def save(self, must_create=False):
|
||||||
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC | getattr(os, 'O_BINARY', 0)
|
# Get the session data now, before we start messing
|
||||||
if must_create:
|
# with the file it is stored within.
|
||||||
flags |= os.O_EXCL
|
|
||||||
# Because this may trigger a load from storage, we must do it before
|
|
||||||
# truncating the file to save.
|
|
||||||
session_data = self._get_session(no_load=must_create)
|
session_data = self._get_session(no_load=must_create)
|
||||||
|
|
||||||
|
session_file_name = self._key_to_file()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
fd = os.open(self._key_to_file(self.session_key), flags)
|
# Make sure the file exists. If it does not already exist, an
|
||||||
try:
|
# empty placeholder file is created.
|
||||||
os.write(fd, self.encode(session_data))
|
flags = os.O_WRONLY | os.O_CREAT | getattr(os, 'O_BINARY', 0)
|
||||||
finally:
|
if must_create:
|
||||||
os.close(fd)
|
flags |= os.O_EXCL
|
||||||
|
fd = os.open(session_file_name, flags)
|
||||||
|
os.close(fd)
|
||||||
|
|
||||||
except OSError, e:
|
except OSError, e:
|
||||||
if must_create and e.errno == errno.EEXIST:
|
if must_create and e.errno == errno.EEXIST:
|
||||||
raise CreateError
|
raise CreateError
|
||||||
raise
|
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
|
pass
|
||||||
|
|
||||||
def exists(self, session_key):
|
def exists(self, session_key):
|
||||||
|
|
Loading…
Reference in New Issue