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

Source Code for Module oauth2client.locked_file

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