#9084 closed Bug (fixed)
File-based session does not store any data on Windows
| Reported by: | mizutori | Owned by: | Joeri Bekker |
|---|---|---|---|
| Component: | contrib.sessions | Version: | 1.5 |
| Severity: | Normal | Keywords: | sprint2013 |
| Cc: | wiser0+djangoproject@… | Triage Stage: | Ready for checkin |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | yes |
| Easy pickings: | no | UI/UX: | no |
Description
Django 1.0 (or trunk r9029) has a bug that does not store any session data using file-based sessions on Windows system.
[1] Sample code
To use file-based sessions, set the SESSION_ENGINE setting to "django.contrib.sessions.backends.file". On Windows system, the following code does not work properly. The session data for "sess_key" will never be passed to the next index2 session.
---[ views.py ]---
def index(request):
request.session['sess_key'] = "yes"
request.session.modified = True
return HttpResponseRedirect('index2.html')
def index2(request):
print "session key="+repr(request.session['sess_key'])
return HttpResponseRedirect('index3.html')
---
[2] My analysis
This is a bug of save() function of django.contrib.sessions.backends.file.SessionStore class. On Windows, os.rename function of Python will raise OSError if the destination file already exists. Whereas on Unix, it will be removed silently. To fix this problem, remove the existing file before renaming if the current operating system is Windows.
[3] My workaround
Apply unlink() function before rename() if the operating system is Windows to fix the save() function of file.SessionStore class.
---[ django/contrib/sessions/backends/file.py ]---
def save(self, must_create=False):
...
try:
...
#00122:
+ if os.name == "nt":
+ os.unlink(session_file_name)
os.rename(output_file_name, session_file_name)
renamed = True
---
# added patch code with "+" mark
Change History (24)
comment:1 by , 17 years ago
| Owner: | changed from to |
|---|---|
| Status: | new → assigned |
| Triage Stage: | Unreviewed → Accepted |
comment:2 by , 17 years ago
| Summary: | [BUG] file-based session does not store any data on Windows → File-based session does not store any data on Windows |
|---|
This is possibly going to be a bit tricky to solve. There are two race conditions that we need to worry about: firstly if another process comes in and reads the session file, the idea is that they see old data, not totally missing data. On windows, there's a small window in which another process will read between the unlink and the rename, which will be a bit confusing to the user.
Secondly, you can't unlink a file that's open in Windows, so there's the potential that the unlink will raise an error (again, because another process is coming in with the same session ID, perhaps due to multiple clicks of a browser button).
In both cases, it might be a case of tough luck -- we catch the error in the latter case and no save happens. But it'd be nice to avoid this if possible. Not sure how, though. Reintroducing locking there isn't going to be at all pleasant, since it's unbelievably tricky to get right in a portable fashion.
comment:4 by , 17 years ago
| Owner: | removed |
|---|---|
| Status: | assigned → new |
I'm not going to have time to work on this for a bit and I want to clean out my list of "reserved" bugs. So giving it back to the pool. Thoughts in comment:2 are still valid, I feel. It's tricky, but I'm happy to review any patches that come along.
comment:5 by , 16 years ago
| Cc: | added |
|---|---|
| Version: | 1.0 → 1.1 |
comment:6 by , 16 years ago
| Has patch: | set |
|---|
comment:8 by , 15 years ago
| Patch needs improvement: | set |
|---|---|
| Severity: | → Normal |
| Type: | → Bug |
Patch needs improvement as per Malcolm's comments.
comment:9 by , 14 years ago
| Easy pickings: | unset |
|---|---|
| UI/UX: | unset |
| Version: | 1.2 → 1.3 |
comment:10 by , 14 years ago
| Has patch: | unset |
|---|---|
| Needs tests: | set |
| Patch needs improvement: | unset |
Please provide a proper patch. This also needs tests.
comment:11 by , 13 years ago
This still happens in 1.4.1. Unfortunately it makes file-based sessions useless on Django in Windows (i.e., development or whatever). Also, it's hard to find, as the exception is caught without warning -- this wouldn't be so bad if it broke noisily.
comment:12 by , 13 years ago
os.rename hasn't improved on Windows. Would it make sense to use shutil.copyfile instead when the platform is Windows?
This isn't atomic, and if I understand correctly, it may still be vulnerable to exceptions if the destination is open for reading at the wrong time.
But we can't do anything about that, besides telling people to use a better OS — ie. POSIX-compliant.
comment:13 by , 13 years ago
| Keywords: | sprint2013 added |
|---|---|
| Owner: | set to |
| Status: | new → assigned |
comment:14 by , 13 years ago
| Has patch: | set |
|---|---|
| Needs tests: | unset |
| Triage Stage: | Accepted → Ready for checkin |
I made a pull request https://github.com/django/django/pull/823 that changes os.rename to shutil.move which does (unlike the Python documentation indicates) an atomic os.rename but falls back to "the best Windows got to an atomic rename" in this specific case: shutil.copy2 and os.unlink.
The file based session save function is actually tested and fails on Windows:
(env) D:\Work\Projects\joeri\django\tests>python runtests.py --settings=test_sqlite sessions
Creating test database for alias 'default'...
Creating test database for alias 'other'...
........................................................................................x........................................................................................F................................
======================================================================
FAIL: test_clearsessions_command (django.contrib.sessions.tests.FileSessionTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\Work\Projects\joeri\django\django\test\utils.py", line 227, in inner
return test_func(*args, **kwargs)
File "D:\Work\Projects\joeri\django\django\contrib\sessions\tests.py", line 444, in test_clearsessions_command
self.assertEqual(1, count_sessions())
AssertionError: 1 != 2
----------------------------------------------------------------------
Ran 210 tests in 0.276s
FAILED (failures=1, expected failures=1)
Destroying test database for alias 'default'...
Destroying test database for alias 'other'...
After patch:
(env) D:\Work\Projects\joeri\django\tests>python runtests.py --settings=test_sqlite sessions Creating test database for alias 'default'... Creating test database for alias 'other'... ........................................................................................x......................................................................................................................... ---------------------------------------------------------------------- Ran 210 tests in 0.235s OK (expected failures=1) Destroying test database for alias 'default'... Destroying test database for alias 'other'...
comment:15 by , 13 years ago
| Resolution: | → fixed |
|---|---|
| Status: | assigned → closed |
comment:16 by , 13 years ago
| Resolution: | fixed |
|---|---|
| Status: | closed → new |
| Version: | 1.3 → 1.5 |
Django 1.5.1 still not fix
comment:20 by , 12 years ago
| Resolution: | fixed |
|---|---|
| Status: | closed → new |
comment:21 by , 12 years ago
| Resolution: | → fixed |
|---|---|
| Status: | new → closed |
The problem is fixed in master and 1.6a1.
It won't be backported to 1.5.X since it's not considered critical.
comment:22 by , 12 years ago
A new function is introduced in Python 3.3, os.replace(), which renames files atomically and cross-platformly: http://bugs.python.org/issue8828
It might be possible to use this function in future, when Django support for Python 2.x is abandoned, or when os.replace() is (ever) backported to Python 2.x.
comment:23 by , 12 years ago
In fact, since MoveFileEx API is considered to be atomic (for example, Java uses it: http://bugs.python.org/issue8828#msg146307), Django could use it through ctypes interface even now (https://github.com/mitsuhiko/python-atomicfile/ - _rename() function - and disregard that its author believes it to be non-atomic, it seems to be atomic actually). The os.replace() function in Python 3.3 is implemented through MoveFileEx anyway.
comment:24 by , 12 years ago
| Patch needs improvement: | set |
|---|
Whoops. Yes, you're right.
os.rename()does need a clear landing zone on Windows. There's been talk from time to time of creatingatomic_renameor something similar in Python that is cross-platform, but it doesn't exist yet.