You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			117 lines
		
	
	
		
			2.9 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			117 lines
		
	
	
		
			2.9 KiB
		
	
	
	
		
			Python
		
	
# Copyright 2020 The Chromium Authors. All rights reserved.
 | 
						|
# Use of this source code is governed by a BSD-style license that can be
 | 
						|
# found in the LICENSE file.
 | 
						|
"""Exclusive filelocking for all supported platforms."""
 | 
						|
 | 
						|
from __future__ import print_function
 | 
						|
 | 
						|
import contextlib
 | 
						|
import logging
 | 
						|
import os
 | 
						|
import sys
 | 
						|
import time
 | 
						|
 | 
						|
 | 
						|
class LockError(Exception):
 | 
						|
  pass
 | 
						|
 | 
						|
 | 
						|
if sys.platform.startswith('win'):
 | 
						|
  # Windows implementation
 | 
						|
  import win32imports
 | 
						|
 | 
						|
  BYTES_TO_LOCK = 1
 | 
						|
 | 
						|
  def _open_file(lockfile):
 | 
						|
    return win32imports.Handle(
 | 
						|
        win32imports.CreateFileW(
 | 
						|
            lockfile,  # lpFileName
 | 
						|
            win32imports.GENERIC_WRITE,  # dwDesiredAccess
 | 
						|
            0,  # dwShareMode=prevent others from opening file
 | 
						|
            None,  # lpSecurityAttributes
 | 
						|
            win32imports.CREATE_ALWAYS,  # dwCreationDisposition
 | 
						|
            win32imports.FILE_ATTRIBUTE_NORMAL,  # dwFlagsAndAttributes
 | 
						|
            None  # hTemplateFile
 | 
						|
        ))
 | 
						|
 | 
						|
  def _close_file(handle):
 | 
						|
    # CloseHandle releases lock too.
 | 
						|
    win32imports.CloseHandle(handle)
 | 
						|
 | 
						|
  def _lock_file(handle):
 | 
						|
    ret = win32imports.LockFileEx(
 | 
						|
        handle,  # hFile
 | 
						|
        win32imports.LOCKFILE_FAIL_IMMEDIATELY
 | 
						|
        | win32imports.LOCKFILE_EXCLUSIVE_LOCK,  # dwFlags
 | 
						|
        0,  #dwReserved
 | 
						|
        BYTES_TO_LOCK,  # nNumberOfBytesToLockLow
 | 
						|
        0,  # nNumberOfBytesToLockHigh
 | 
						|
        win32imports.Overlapped()  # lpOverlapped
 | 
						|
    )
 | 
						|
    # LockFileEx returns result as bool, which is converted into an integer
 | 
						|
    # (1 == successful; 0 == not successful)
 | 
						|
    if ret == 0:
 | 
						|
      error_code = win32imports.GetLastError()
 | 
						|
      raise OSError('Failed to lock handle (error code: %d).' % error_code)
 | 
						|
else:
 | 
						|
  # Unix implementation
 | 
						|
  import fcntl
 | 
						|
 | 
						|
  def _open_file(lockfile):
 | 
						|
    open_flags = (os.O_CREAT | os.O_WRONLY)
 | 
						|
    return os.open(lockfile, open_flags, 0o644)
 | 
						|
 | 
						|
  def _close_file(fd):
 | 
						|
    os.close(fd)
 | 
						|
 | 
						|
  def _lock_file(fd):
 | 
						|
    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
 | 
						|
 | 
						|
 | 
						|
def _try_lock(lockfile):
 | 
						|
  f = _open_file(lockfile)
 | 
						|
  try:
 | 
						|
    _lock_file(f)
 | 
						|
  except Exception:
 | 
						|
    _close_file(f)
 | 
						|
    raise
 | 
						|
  return lambda: _close_file(f)
 | 
						|
 | 
						|
 | 
						|
def _lock(path, timeout=0):
 | 
						|
  """_lock returns function to release the lock if locking was successful.
 | 
						|
 | 
						|
  _lock also implements simple retry logic."""
 | 
						|
  elapsed = 0
 | 
						|
  while True:
 | 
						|
    try:
 | 
						|
      return _try_lock(path + '.locked')
 | 
						|
    except (OSError, IOError) as e:
 | 
						|
      if elapsed < timeout:
 | 
						|
        sleep_time = min(10, timeout - elapsed)
 | 
						|
        logging.info(
 | 
						|
            'Could not create git cache lockfile; '
 | 
						|
            'will retry after sleep(%d).', sleep_time)
 | 
						|
        elapsed += sleep_time
 | 
						|
        time.sleep(sleep_time)
 | 
						|
        continue
 | 
						|
      raise LockError("Error locking %s (err: %s)" % (path, str(e)))
 | 
						|
 | 
						|
 | 
						|
@contextlib.contextmanager
 | 
						|
def lock(path, timeout=0):
 | 
						|
  """Get exclusive lock to path.
 | 
						|
 | 
						|
  Usage:
 | 
						|
    import lockfile
 | 
						|
    with lockfile.lock(path, timeout):
 | 
						|
      # Do something
 | 
						|
      pass
 | 
						|
 | 
						|
   """
 | 
						|
  release_fn = _lock(path, timeout)
 | 
						|
  try:
 | 
						|
    yield
 | 
						|
  finally:
 | 
						|
    release_fn()
 |