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