Package oauth2client :: Module locked_file
[hide private]
[frames] | no frames]

Source Code for Module oauth2client.locked_file

  1  # Copyright 2011 Google Inc. All Rights Reserved. 
  2   
  3  """Locked file interface that should work on Unix and Windows pythons. 
  4   
  5  This module first tries to use fcntl locking to ensure serialized access 
  6  to a file, then falls back on a lock file if that is unavialable. 
  7   
  8  Usage: 
  9      f = LockedFile('filename', 'r+b', 'rb') 
 10      f.open_and_lock() 
 11      if f.is_locked(): 
 12        print 'Acquired filename with r+b mode' 
 13        f.file_handle().write('locked data') 
 14      else: 
 15        print 'Aquired filename with rb mode' 
 16      f.unlock_and_close() 
 17  """ 
 18   
 19  __author__ = 'cache@google.com (David T McWherter)' 
 20   
 21  import errno 
 22  import logging 
 23  import os 
 24  import time 
 25   
 26  logger = logging.getLogger(__name__) 
 27   
 28   
29 -class AlreadyLockedException(Exception):
30 """Trying to lock a file that has already been locked by the LockedFile.""" 31 pass
32 33
34 -class _Opener(object):
35 """Base class for different locking primitives.""" 36
37 - def __init__(self, filename, mode, fallback_mode):
38 """Create an Opener. 39 40 Args: 41 filename: string, The pathname of the file. 42 mode: string, The preferred mode to access the file with. 43 fallback_mode: string, The mode to use if locking fails. 44 """ 45 self._locked = False 46 self._filename = filename 47 self._mode = mode 48 self._fallback_mode = fallback_mode 49 self._fh = None
50
51 - def is_locked(self):
52 """Was the file locked.""" 53 return self._locked
54
55 - def file_handle(self):
56 """The file handle to the file. Valid only after opened.""" 57 return self._fh
58
59 - def filename(self):
60 """The filename that is being locked.""" 61 return self._filename
62
63 - def open_and_lock(self, timeout, delay):
64 """Open the file and lock it. 65 66 Args: 67 timeout: float, How long to try to lock for. 68 delay: float, How long to wait between retries. 69 """ 70 pass
71
72 - def unlock_and_close(self):
73 """Unlock and close the file.""" 74 pass
75 76
77 -class _PosixOpener(_Opener):
78 """Lock files using Posix advisory lock files.""" 79
80 - def open_and_lock(self, timeout, delay):
81 """Open the file and lock it. 82 83 Tries to create a .lock file next to the file we're trying to open. 84 85 Args: 86 timeout: float, How long to try to lock for. 87 delay: float, How long to wait between retries. 88 89 Raises: 90 AlreadyLockedException: if the lock is already acquired. 91 IOError: if the open fails. 92 """ 93 if self._locked: 94 raise AlreadyLockedException('File %s is already locked' % 95 self._filename) 96 self._locked = False 97 98 try: 99 self._fh = open(self._filename, self._mode) 100 except IOError, e: 101 # If we can't access with _mode, try _fallback_mode and don't lock. 102 if e.errno == errno.EACCES: 103 self._fh = open(self._filename, self._fallback_mode) 104 return 105 106 lock_filename = self._posix_lockfile(self._filename) 107 start_time = time.time() 108 while True: 109 try: 110 self._lock_fd = os.open(lock_filename, 111 os.O_CREAT|os.O_EXCL|os.O_RDWR) 112 self._locked = True 113 break 114 115 except OSError, e: 116 if e.errno != errno.EEXIST: 117 raise 118 if (time.time() - start_time) >= timeout: 119 logger.warn('Could not acquire lock %s in %s seconds' % ( 120 lock_filename, timeout)) 121 # Close the file and open in fallback_mode. 122 if self._fh: 123 self._fh.close() 124 self._fh = open(self._filename, self._fallback_mode) 125 return 126 time.sleep(delay)
127
128 - def unlock_and_close(self):
129 """Unlock a file by removing the .lock file, and close the handle.""" 130 if self._locked: 131 lock_filename = self._posix_lockfile(self._filename) 132 os.unlink(lock_filename) 133 os.close(self._lock_fd) 134 self._locked = False 135 self._lock_fd = None 136 if self._fh: 137 self._fh.close()
138
139 - def _posix_lockfile(self, filename):
140 """The name of the lock file to use for posix locking.""" 141 return '%s.lock' % filename
142 143 144 try: 145 import fcntl 146
147 - class _FcntlOpener(_Opener):
148 """Open, lock, and unlock a file using fcntl.lockf.""" 149
150 - def open_and_lock(self, timeout, delay):
151 """Open the file and lock it. 152 153 Args: 154 timeout: float, How long to try to lock for. 155 delay: float, How long to wait between retries 156 157 Raises: 158 AlreadyLockedException: if the lock is already acquired. 159 IOError: if the open fails. 160 """ 161 if self._locked: 162 raise AlreadyLockedException('File %s is already locked' % 163 self._filename) 164 start_time = time.time() 165 166 try: 167 self._fh = open(self._filename, self._mode) 168 except IOError, e: 169 # If we can't access with _mode, try _fallback_mode and don't lock. 170 if e.errno == errno.EACCES: 171 self._fh = open(self._filename, self._fallback_mode) 172 return 173 174 # We opened in _mode, try to lock the file. 175 while True: 176 try: 177 fcntl.lockf(self._fh.fileno(), fcntl.LOCK_EX) 178 self._locked = True 179 return 180 except IOError, e: 181 # If not retrying, then just pass on the error. 182 if timeout == 0: 183 raise e 184 if e.errno != errno.EACCES: 185 raise e 186 # We could not acquire the lock. Try again. 187 if (time.time() - start_time) >= timeout: 188 logger.warn('Could not lock %s in %s seconds' % ( 189 self._filename, timeout)) 190 if self._fh: 191 self._fh.close() 192 self._fh = open(self._filename, self._fallback_mode) 193 return 194 time.sleep(delay)
195
196 - def unlock_and_close(self):
197 """Close and unlock the file using the fcntl.lockf primitive.""" 198 if self._locked: 199 fcntl.lockf(self._fh.fileno(), fcntl.LOCK_UN) 200 self._locked = False 201 if self._fh: 202 self._fh.close()
203 except ImportError: 204 _FcntlOpener = None 205 206 207 try: 208 import pywintypes 209 import win32con 210 import win32file 211
212 - class _Win32Opener(_Opener):
213 """Open, lock, and unlock a file using windows primitives.""" 214 215 # Error #33: 216 # 'The process cannot access the file because another process' 217 FILE_IN_USE_ERROR = 33 218 219 # Error #158: 220 # 'The segment is already unlocked.' 221 FILE_ALREADY_UNLOCKED_ERROR = 158 222
223 - def open_and_lock(self, timeout, delay):
224 """Open the file and lock it. 225 226 Args: 227 timeout: float, How long to try to lock for. 228 delay: float, How long to wait between retries 229 230 Raises: 231 AlreadyLockedException: if the lock is already acquired. 232 IOError: if the open fails. 233 """ 234 if self._locked: 235 raise AlreadyLockedException('File %s is already locked' % 236 self._filename) 237 start_time = time.time() 238 239 try: 240 self._fh = open(self._filename, self._mode) 241 except IOError, e: 242 # If we can't access with _mode, try _fallback_mode and don't lock. 243 if e.errno == errno.EACCES: 244 self._fh = open(self._filename, self._fallback_mode) 245 return 246 247 # We opened in _mode, try to lock the file. 248 while True: 249 try: 250 hfile = win32file._get_osfhandle(self._fh.fileno()) 251 win32file.LockFileEx( 252 hfile, 253 (win32con.LOCKFILE_FAIL_IMMEDIATELY| 254 win32con.LOCKFILE_EXCLUSIVE_LOCK), 0, -0x10000, 255 pywintypes.OVERLAPPED()) 256 self._locked = True 257 return 258 except pywintypes.error, e: 259 if timeout == 0: 260 raise e 261 262 # If the error is not that the file is already in use, raise. 263 if e[0] != _Win32Opener.FILE_IN_USE_ERROR: 264 raise 265 266 # We could not acquire the lock. Try again. 267 if (time.time() - start_time) >= timeout: 268 logger.warn('Could not lock %s in %s seconds' % ( 269 self._filename, timeout)) 270 if self._fh: 271 self._fh.close() 272 self._fh = open(self._filename, self._fallback_mode) 273 return 274 time.sleep(delay)
275
276 - def unlock_and_close(self):
277 """Close and unlock the file using the win32 primitive.""" 278 if self._locked: 279 try: 280 hfile = win32file._get_osfhandle(self._fh.fileno()) 281 win32file.UnlockFileEx(hfile, 0, -0x10000, pywintypes.OVERLAPPED()) 282 except pywintypes.error, e: 283 if e[0] != _Win32Opener.FILE_ALREADY_UNLOCKED_ERROR: 284 raise 285 self._locked = False 286 if self._fh: 287 self._fh.close()
288 except ImportError: 289 _Win32Opener = None 290 291
292 -class LockedFile(object):
293 """Represent a file that has exclusive access.""" 294
295 - def __init__(self, filename, mode, fallback_mode, use_native_locking=True):
296 """Construct a LockedFile. 297 298 Args: 299 filename: string, The path of the file to open. 300 mode: string, The mode to try to open the file with. 301 fallback_mode: string, The mode to use if locking fails. 302 use_native_locking: bool, Whether or not fcntl/win32 locking is used. 303 """ 304 opener = None 305 if not opener and use_native_locking: 306 if _Win32Opener: 307 opener = _Win32Opener(filename, mode, fallback_mode) 308 if _FcntlOpener: 309 opener = _FcntlOpener(filename, mode, fallback_mode) 310 311 if not opener: 312 opener = _PosixOpener(filename, mode, fallback_mode) 313 314 self._opener = opener
315
316 - def filename(self):
317 """Return the filename we were constructed with.""" 318 return self._opener._filename
319
320 - def file_handle(self):
321 """Return the file_handle to the opened file.""" 322 return self._opener.file_handle()
323
324 - def is_locked(self):
325 """Return whether we successfully locked the file.""" 326 return self._opener.is_locked()
327
328 - def open_and_lock(self, timeout=0, delay=0.05):
329 """Open the file, trying to lock it. 330 331 Args: 332 timeout: float, The number of seconds to try to acquire the lock. 333 delay: float, The number of seconds to wait between retry attempts. 334 335 Raises: 336 AlreadyLockedException: if the lock is already acquired. 337 IOError: if the open fails. 338 """ 339 self._opener.open_and_lock(timeout, delay)
340
341 - def unlock_and_close(self):
342 """Unlock and close a file.""" 343 self._opener.unlock_and_close()
344