summaryrefslogtreecommitdiff
path: root/lib/spack/llnl/util/lock.py
diff options
context:
space:
mode:
authorTodd Gamblin <tgamblin@llnl.gov>2022-07-30 15:19:18 -0700
committerTodd Gamblin <tgamblin@llnl.gov>2022-07-31 13:29:20 -0700
commitf52f6e99dbf1131886a80112b8c79dfc414afb7c (patch)
tree05cb7d64b2395922f2f24683da49f472075be12c /lib/spack/llnl/util/lock.py
parent549ba1ed32372c67fc57271cde3797d58b7dec6e (diff)
downloadspack-f52f6e99dbf1131886a80112b8c79dfc414afb7c.tar.gz
spack-f52f6e99dbf1131886a80112b8c79dfc414afb7c.tar.bz2
spack-f52f6e99dbf1131886a80112b8c79dfc414afb7c.tar.xz
spack-f52f6e99dbf1131886a80112b8c79dfc414afb7c.zip
black: reformat entire repository with black
Diffstat (limited to 'lib/spack/llnl/util/lock.py')
-rw-r--r--lib/spack/llnl/util/lock.py164
1 files changed, 85 insertions, 79 deletions
diff --git a/lib/spack/llnl/util/lock.py b/lib/spack/llnl/util/lock.py
index 1ff7ceec64..0682ce059a 100644
--- a/lib/spack/llnl/util/lock.py
+++ b/lib/spack/llnl/util/lock.py
@@ -15,22 +15,22 @@ import llnl.util.tty as tty
import spack.util.string
-if sys.platform != 'win32':
+if sys.platform != "win32":
import fcntl
__all__ = [
- 'Lock',
- 'LockDowngradeError',
- 'LockUpgradeError',
- 'LockTransaction',
- 'WriteTransaction',
- 'ReadTransaction',
- 'LockError',
- 'LockTimeoutError',
- 'LockPermissionError',
- 'LockROFileError',
- 'CantCreateLockError'
+ "Lock",
+ "LockDowngradeError",
+ "LockUpgradeError",
+ "LockTransaction",
+ "WriteTransaction",
+ "ReadTransaction",
+ "LockError",
+ "LockTimeoutError",
+ "LockPermissionError",
+ "LockROFileError",
+ "CantCreateLockError",
]
@@ -47,6 +47,7 @@ class OpenFile(object):
the file descriptor from the file handle if needed -- or we could make this track
file descriptors as well in the future.
"""
+
def __init__(self, fh):
self.fh = fh
self.refs = 0
@@ -92,11 +93,11 @@ class OpenFileTracker(object):
path (str): path to lock file we want a filehandle for
"""
# Open writable files as 'r+' so we can upgrade to write later
- os_mode, fh_mode = (os.O_RDWR | os.O_CREAT), 'r+'
+ os_mode, fh_mode = (os.O_RDWR | os.O_CREAT), "r+"
pid = os.getpid()
open_file = None # OpenFile object, if there is one
- stat = None # stat result for the lockfile, if it exists
+ stat = None # stat result for the lockfile, if it exists
try:
# see whether we've seen this inode/pid before
@@ -109,7 +110,7 @@ class OpenFileTracker(object):
raise
# path does not exist -- fail if we won't be able to create it
- parent = os.path.dirname(path) or '.'
+ parent = os.path.dirname(path) or "."
if not os.access(parent, os.W_OK):
raise CantCreateLockError(path)
@@ -119,7 +120,7 @@ class OpenFileTracker(object):
# we know path exists but not if it's writable. If it's read-only,
# only open the file for reading (and fail if we're trying to get
# an exclusive (write) lock on it)
- os_mode, fh_mode = os.O_RDONLY, 'r'
+ os_mode, fh_mode = os.O_RDONLY, "r"
fd = os.open(path, os_mode)
fh = os.fdopen(fd, fh_mode)
@@ -162,10 +163,10 @@ file_tracker = OpenFileTracker()
def _attempts_str(wait_time, nattempts):
# Don't print anything if we succeeded on the first try
if nattempts <= 1:
- return ''
+ return ""
- attempts = spack.util.string.plural(nattempts, 'attempt')
- return ' after {0:0.2f}s and {1}'.format(wait_time, attempts)
+ attempts = spack.util.string.plural(nattempts, "attempt")
+ return " after {0:0.2f}s and {1}".format(wait_time, attempts)
class LockType(object):
@@ -188,8 +189,7 @@ class LockType(object):
@staticmethod
def is_valid(op):
- return op == LockType.READ \
- or op == LockType.WRITE
+ return op == LockType.READ or op == LockType.WRITE
class Lock(object):
@@ -207,8 +207,7 @@ class Lock(object):
overlapping byte ranges in the same file).
"""
- def __init__(self, path, start=0, length=0, default_timeout=None,
- debug=False, desc=''):
+ def __init__(self, path, start=0, length=0, default_timeout=None, debug=False, desc=""):
"""Construct a new lock on the file at ``path``.
By default, the lock applies to the whole file. Optionally,
@@ -243,7 +242,7 @@ class Lock(object):
self.debug = debug
# optional debug description
- self.desc = ' ({0})'.format(desc) if desc else ''
+ self.desc = " ({0})".format(desc) if desc else ""
# If the user doesn't set a default timeout, or if they choose
# None, 0, etc. then lock attempts will not time out (unless the
@@ -280,17 +279,17 @@ class Lock(object):
def __repr__(self):
"""Formal representation of the lock."""
- rep = '{0}('.format(self.__class__.__name__)
+ rep = "{0}(".format(self.__class__.__name__)
for attr, value in self.__dict__.items():
- rep += '{0}={1}, '.format(attr, value.__repr__())
- return '{0})'.format(rep.strip(', '))
+ rep += "{0}={1}, ".format(attr, value.__repr__())
+ return "{0})".format(rep.strip(", "))
def __str__(self):
"""Readable string (with key fields) of the lock."""
- location = '{0}[{1}:{2}]'.format(self.path, self._start, self._length)
- timeout = 'timeout={0}'.format(self.default_timeout)
- activity = '#reads={0}, #writes={1}'.format(self._reads, self._writes)
- return '({0}, {1}, {2})'.format(location, timeout, activity)
+ location = "{0}[{1}:{2}]".format(self.path, self._start, self._length)
+ timeout = "timeout={0}".format(self.default_timeout)
+ activity = "#reads={0}, #writes={1}".format(self._reads, self._writes)
+ return "({0}, {1}, {2})".format(location, timeout, activity)
def _lock(self, op, timeout=None):
"""This takes a lock using POSIX locks (``fcntl.lockf``).
@@ -305,7 +304,7 @@ class Lock(object):
assert LockType.is_valid(op)
op_str = LockType.to_str(op)
- self._log_acquiring('{0} LOCK'.format(op_str))
+ self._log_acquiring("{0} LOCK".format(op_str))
timeout = timeout or self.default_timeout
# Create file and parent directories if they don't exist.
@@ -313,14 +312,16 @@ class Lock(object):
self._ensure_parent_directory()
self._file = file_tracker.get_fh(self.path)
- if LockType.to_module(op) == fcntl.LOCK_EX and self._file.mode == 'r':
+ if LockType.to_module(op) == fcntl.LOCK_EX and self._file.mode == "r":
# Attempt to upgrade to write lock w/a read-only file.
# If the file were writable, we'd have opened it 'r+'
raise LockROFileError(self.path)
- self._log_debug("{0} locking [{1}:{2}]: timeout {3} sec"
- .format(op_str.lower(), self._start, self._length,
- timeout))
+ self._log_debug(
+ "{0} locking [{1}:{2}]: timeout {3} sec".format(
+ op_str.lower(), self._start, self._length, timeout
+ )
+ )
poll_intervals = iter(Lock._poll_interval_generator())
start_time = time.time()
@@ -339,8 +340,7 @@ class Lock(object):
total_wait_time = time.time() - start_time
return total_wait_time, num_attempts
- raise LockTimeoutError("Timed out waiting for a {0} lock."
- .format(op_str.lower()))
+ raise LockTimeoutError("Timed out waiting for a {0} lock.".format(op_str.lower()))
def _poll_lock(self, op):
"""Attempt to acquire the lock in a non-blocking manner. Return whether
@@ -349,16 +349,19 @@ class Lock(object):
module_op = LockType.to_module(op)
try:
# Try to get the lock (will raise if not available.)
- fcntl.lockf(self._file, module_op | fcntl.LOCK_NB,
- self._length, self._start, os.SEEK_SET)
+ fcntl.lockf(
+ self._file, module_op | fcntl.LOCK_NB, self._length, self._start, os.SEEK_SET
+ )
# help for debugging distributed locking
if self.debug:
# All locks read the owner PID and host
self._read_log_debug_data()
- self._log_debug('{0} locked {1} [{2}:{3}] (owner={4})'
- .format(LockType.to_str(op), self.path,
- self._start, self._length, self.pid))
+ self._log_debug(
+ "{0} locked {1} [{2}:{3}] (owner={4})".format(
+ LockType.to_str(op), self.path, self._start, self._length, self.pid
+ )
+ )
# Exclusive locks write their PID/host
if module_op == fcntl.LOCK_EX:
@@ -378,14 +381,13 @@ class Lock(object):
# relative paths to lockfiles in the current directory have no parent
if not parent:
- return '.'
+ return "."
try:
os.makedirs(parent)
except OSError as e:
# makedirs can fail when diretory already exists.
- if not (e.errno == errno.EEXIST and os.path.isdir(parent) or
- e.errno == errno.EISDIR):
+ if not (e.errno == errno.EEXIST and os.path.isdir(parent) or e.errno == errno.EISDIR):
raise
return parent
@@ -396,9 +398,9 @@ class Lock(object):
line = self._file.read()
if line:
- pid, host = line.strip().split(',')
- _, _, self.pid = pid.rpartition('=')
- _, _, self.host = host.rpartition('=')
+ pid, host = line.strip().split(",")
+ _, _, self.pid = pid.rpartition("=")
+ _, _, self.host = host.rpartition("=")
self.pid = int(self.pid)
def _write_log_debug_data(self):
@@ -423,8 +425,7 @@ class Lock(object):
be masquerading as write locks, but this removes either.
"""
- fcntl.lockf(self._file, fcntl.LOCK_UN,
- self._length, self._start, os.SEEK_SET)
+ fcntl.lockf(self._file, fcntl.LOCK_UN, self._length, self._start, os.SEEK_SET)
file_tracker.release_fh(self.path)
self._file = None
@@ -449,7 +450,7 @@ class Lock(object):
wait_time, nattempts = self._lock(LockType.READ, timeout=timeout)
self._reads += 1
# Log if acquired, which includes counts when verbose
- self._log_acquired('READ LOCK', wait_time, nattempts)
+ self._log_acquired("READ LOCK", wait_time, nattempts)
return True
else:
# Increment the read count for nested lock tracking
@@ -474,7 +475,7 @@ class Lock(object):
wait_time, nattempts = self._lock(LockType.WRITE, timeout=timeout)
self._writes += 1
# Log if acquired, which includes counts when verbose
- self._log_acquired('WRITE LOCK', wait_time, nattempts)
+ self._log_acquired("WRITE LOCK", wait_time, nattempts)
# return True only if we weren't nested in a read lock.
# TODO: we may need to return two values: whether we got
@@ -561,7 +562,7 @@ class Lock(object):
"""
assert self._reads > 0
- locktype = 'READ LOCK'
+ locktype = "READ LOCK"
if self._reads == 1 and self._writes == 0:
self._log_releasing(locktype)
@@ -569,7 +570,7 @@ class Lock(object):
release_fn = release_fn or true_fn
result = release_fn()
- self._unlock() # can raise LockError.
+ self._unlock() # can raise LockError.
self._reads = 0
self._log_released(locktype)
return result
@@ -597,14 +598,14 @@ class Lock(object):
assert self._writes > 0
release_fn = release_fn or true_fn
- locktype = 'WRITE LOCK'
+ locktype = "WRITE LOCK"
if self._writes == 1 and self._reads == 0:
self._log_releasing(locktype)
# we need to call release_fn before releasing the lock
result = release_fn()
- self._unlock() # can raise LockError.
+ self._unlock() # can raise LockError.
self._writes = 0
self._log_released(locktype)
return result
@@ -625,56 +626,55 @@ class Lock(object):
raise LockError("Attempting to cleanup active lock.")
def _get_counts_desc(self):
- return '(reads {0}, writes {1})'.format(self._reads, self._writes) \
- if tty.is_verbose() else ''
+ return (
+ "(reads {0}, writes {1})".format(self._reads, self._writes) if tty.is_verbose() else ""
+ )
def _log_acquired(self, locktype, wait_time, nattempts):
attempts_part = _attempts_str(wait_time, nattempts)
now = datetime.now()
- desc = 'Acquired at %s' % now.strftime("%H:%M:%S.%f")
- self._log_debug(self._status_msg(locktype, '{0}{1}'
- .format(desc, attempts_part)))
+ desc = "Acquired at %s" % now.strftime("%H:%M:%S.%f")
+ self._log_debug(self._status_msg(locktype, "{0}{1}".format(desc, attempts_part)))
def _log_acquiring(self, locktype):
- self._log_debug(self._status_msg(locktype, 'Acquiring'), level=3)
+ self._log_debug(self._status_msg(locktype, "Acquiring"), level=3)
def _log_debug(self, *args, **kwargs):
"""Output lock debug messages."""
- kwargs['level'] = kwargs.get('level', 2)
+ kwargs["level"] = kwargs.get("level", 2)
tty.debug(*args, **kwargs)
def _log_downgraded(self, wait_time, nattempts):
attempts_part = _attempts_str(wait_time, nattempts)
now = datetime.now()
- desc = 'Downgraded at %s' % now.strftime("%H:%M:%S.%f")
- self._log_debug(self._status_msg('READ LOCK', '{0}{1}'
- .format(desc, attempts_part)))
+ desc = "Downgraded at %s" % now.strftime("%H:%M:%S.%f")
+ self._log_debug(self._status_msg("READ LOCK", "{0}{1}".format(desc, attempts_part)))
def _log_downgrading(self):
- self._log_debug(self._status_msg('WRITE LOCK', 'Downgrading'), level=3)
+ self._log_debug(self._status_msg("WRITE LOCK", "Downgrading"), level=3)
def _log_released(self, locktype):
now = datetime.now()
- desc = 'Released at %s' % now.strftime("%H:%M:%S.%f")
+ desc = "Released at %s" % now.strftime("%H:%M:%S.%f")
self._log_debug(self._status_msg(locktype, desc))
def _log_releasing(self, locktype):
- self._log_debug(self._status_msg(locktype, 'Releasing'), level=3)
+ self._log_debug(self._status_msg(locktype, "Releasing"), level=3)
def _log_upgraded(self, wait_time, nattempts):
attempts_part = _attempts_str(wait_time, nattempts)
now = datetime.now()
- desc = 'Upgraded at %s' % now.strftime("%H:%M:%S.%f")
- self._log_debug(self._status_msg('WRITE LOCK', '{0}{1}'.
- format(desc, attempts_part)))
+ desc = "Upgraded at %s" % now.strftime("%H:%M:%S.%f")
+ self._log_debug(self._status_msg("WRITE LOCK", "{0}{1}".format(desc, attempts_part)))
def _log_upgrading(self):
- self._log_debug(self._status_msg('READ LOCK', 'Upgrading'), level=3)
+ self._log_debug(self._status_msg("READ LOCK", "Upgrading"), level=3)
def _status_msg(self, locktype, status):
- status_desc = '[{0}] {1}'.format(status, self._get_counts_desc())
- return '{0}{1.desc}: {1.path}[{1._start}:{1._length}] {2}'.format(
- locktype, self, status_desc)
+ status_desc = "[{0}] {1}".format(status, self._get_counts_desc())
+ return "{0}{1.desc}: {1.path}[{1._start}:{1._length}] {2}".format(
+ locktype, self, status_desc
+ )
class LockTransaction(object):
@@ -715,7 +715,7 @@ class LockTransaction(object):
def __enter__(self):
if self._enter() and self._acquire_fn:
self._as = self._acquire_fn()
- if hasattr(self._as, '__enter__'):
+ if hasattr(self._as, "__enter__"):
return self._as.__enter__()
else:
return self._as
@@ -727,7 +727,7 @@ class LockTransaction(object):
if self._release_fn is not None:
return self._release_fn(type, value, traceback)
- if self._as and hasattr(self._as, '__exit__'):
+ if self._as and hasattr(self._as, "__exit__"):
if self._as.__exit__(type, value, traceback):
suppress = True
@@ -739,6 +739,7 @@ class LockTransaction(object):
class ReadTransaction(LockTransaction):
"""LockTransaction context manager that does a read and releases it."""
+
def _enter(self):
return self._lock.acquire_read(self._timeout)
@@ -748,6 +749,7 @@ class ReadTransaction(LockTransaction):
class WriteTransaction(LockTransaction):
"""LockTransaction context manager that does a write and releases it."""
+
def _enter(self):
return self._lock.acquire_write(self._timeout)
@@ -761,6 +763,7 @@ class LockError(Exception):
class LockDowngradeError(LockError):
"""Raised when unable to downgrade from a write to a read lock."""
+
def __init__(self, path):
msg = "Cannot downgrade lock from write to read on file: %s" % path
super(LockDowngradeError, self).__init__(msg)
@@ -776,6 +779,7 @@ class LockTimeoutError(LockError):
class LockUpgradeError(LockError):
"""Raised when unable to upgrade from a read to a write lock."""
+
def __init__(self, path):
msg = "Cannot upgrade lock from read to write on file: %s" % path
super(LockUpgradeError, self).__init__(msg)
@@ -787,6 +791,7 @@ class LockPermissionError(LockError):
class LockROFileError(LockPermissionError):
"""Tried to take an exclusive lock on a read-only file."""
+
def __init__(self, path):
msg = "Can't take write lock on read-only file: %s" % path
super(LockROFileError, self).__init__(msg)
@@ -794,6 +799,7 @@ class LockROFileError(LockPermissionError):
class CantCreateLockError(LockPermissionError):
"""Attempt to create a lock in an unwritable location."""
+
def __init__(self, path):
msg = "cannot create lock '%s': " % path
msg += "file does not exist and location is not writable"