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