Changeset 8306
- Timestamp:
- 08/11/08 11:51:18 (4 months ago)
- Files:
-
- django/trunk/django/core/files/locks.py (modified) (1 diff)
- django/trunk/django/core/files/move.py (modified) (1 diff)
- django/trunk/django/core/files/storage.py (modified) (2 diffs)
- django/trunk/tests/regressiontests/file_storage/tests.py (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
django/trunk/django/core/files/locks.py
r7814 r8306 41 41 pass 42 42 43 def fd(f): 44 """Get a filedescriptor from something which could be a file or an fd.""" 45 return hasattr(f, 'fileno') and f.fileno() or f 46 43 47 if system_type == 'nt': 44 48 def lock(file, flags): 45 hfile = win32file._get_osfhandle(f ile.fileno())49 hfile = win32file._get_osfhandle(fd(file)) 46 50 win32file.LockFileEx(hfile, flags, 0, -0x10000, __overlapped) 47 51 48 52 def unlock(file): 49 hfile = win32file._get_osfhandle(f ile.fileno())53 hfile = win32file._get_osfhandle(fd(file)) 50 54 win32file.UnlockFileEx(hfile, 0, -0x10000, __overlapped) 51 55 elif system_type == 'posix': 52 56 def lock(file, flags): 53 fcntl.flock(f ile.fileno(), flags)57 fcntl.flock(fd(file), flags) 54 58 55 59 def unlock(file): 56 fcntl.flock(f ile.fileno(), fcntl.LOCK_UN)60 fcntl.flock(fd(file), fcntl.LOCK_UN) 57 61 else: 58 62 # File locking is not supported. django/trunk/django/core/files/move.py
r7814 r8306 45 45 46 46 # If the built-in didn't work, do it the hard way. 47 new_file = open(new_file_name, 'wb') 48 locks.lock(new_file, locks.LOCK_EX) 49 old_file = open(old_file_name, 'rb') 50 current_chunk = None 51 52 while current_chunk != '': 53 current_chunk = old_file.read(chunk_size) 54 new_file.write(current_chunk) 55 56 new_file.close() 57 old_file.close() 47 fd = os.open(new_file_name, os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, 'O_BINARY', 0)) 48 try: 49 locks.lock(fd, locks.LOCK_EX) 50 old_file = open(old_file_name, 'rb') 51 current_chunk = None 52 while current_chunk != '': 53 current_chunk = old_file.read(chunk_size) 54 os.write(fd, current_chunk) 55 finally: 56 locks.unlock(fd) 57 os.close(fd) 58 old_file.close() 58 59 59 60 os.remove(old_file_name) django/trunk/django/core/files/storage.py
r8291 r8306 40 40 if name is None: 41 41 name = content.name 42 42 43 name = self.get_available_name(name) 43 44 self._save(name, content) 44 name = self._save(name, content) 45 45 46 46 # Store filenames with forward slashes, even on Windows … … 136 136 elif not os.path.isdir(directory): 137 137 raise IOError("%s exists and is not a directory." % directory) 138 139 if hasattr(content, 'temporary_file_path'): 140 # This file has a file path that we can move. 141 file_move_safe(content.temporary_file_path(), full_path) 142 content.close() 143 else: 144 # This is a normal uploadedfile that we can stream. 145 fp = open(full_path, 'wb') 146 locks.lock(fp, locks.LOCK_EX) 147 for chunk in content.chunks(): 148 fp.write(chunk) 149 locks.unlock(fp) 150 fp.close() 138 139 # There's a potential race condition between get_available_name and 140 # saving the file; it's possible that two threads might return the 141 # same name, at which point all sorts of fun happens. So we need to 142 # try to create the file, but if it already exists we have to go back 143 # to get_available_name() and try again. 144 145 while True: 146 try: 147 # This file has a file path that we can move. 148 if hasattr(content, 'temporary_file_path'): 149 file_move_safe(content.temporary_file_path(), full_path) 150 content.close() 151 152 # This is a normal uploadedfile that we can stream. 153 else: 154 # This fun binary flag incantation makes os.open throw an 155 # OSError if the file already exists before we open it. 156 fd = os.open(full_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, 'O_BINARY', 0)) 157 try: 158 locks.lock(fd, locks.LOCK_EX) 159 for chunk in content.chunks(): 160 os.write(fd, chunk) 161 finally: 162 locks.unlock(fd) 163 os.close(fd) 164 except OSError: 165 # Ooops, we need a new file name. 166 name = self.get_available_name(name) 167 full_path = self.path(name) 168 else: 169 # OK, the file save worked. Break out of the loop. 170 break 171 172 return name 151 173 152 174 def delete(self, name): django/trunk/tests/regressiontests/file_storage/tests.py
r8244 r8306 65 65 >>> custom_storage.delete(second) 66 66 """ 67 68 # Tests for a race condition on file saving (#4948). 69 # This is written in such a way that it'll always pass on platforms 70 # without threading. 71 72 import time 73 from unittest import TestCase 74 from django.core.files.base import ContentFile 75 from models import temp_storage 76 try: 77 import threading 78 except ImportError: 79 import dummy_threading as threading 80 81 class SlowFile(ContentFile): 82 def chunks(self): 83 time.sleep(1) 84 return super(ContentFile, self).chunks() 85 86 class FileSaveRaceConditionTest(TestCase): 87 def setUp(self): 88 self.thread = threading.Thread(target=self.save_file, args=['conflict']) 89 90 def save_file(self, name): 91 name = temp_storage.save(name, SlowFile("Data")) 92 93 def test_race_condition(self): 94 self.thread.start() 95 name = self.save_file('conflict') 96 self.thread.join() 97 self.assert_(temp_storage.exists('conflict')) 98 self.assert_(temp_storage.exists('conflict_')) 99 temp_storage.delete('conflict') 100 temp_storage.delete('conflict_') 101
