80 lines
1.9 KiB
Python
80 lines
1.9 KiB
Python
# Copyright 2023 The Chromium Authors
|
|
# 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.
|
|
|
|
Copied from third_party/depot_tools/lockfile.py.
|
|
"""
|
|
|
|
import contextlib
|
|
import fcntl
|
|
import logging
|
|
import os
|
|
import time
|
|
|
|
|
|
class LockError(Exception):
|
|
"""Error raised if timeout or lock (without timeout) fails."""
|
|
|
|
|
|
def _open_file(lockfile):
|
|
open_flags = (os.O_CREAT | os.O_WRONLY)
|
|
return os.open(lockfile, open_flags, 0o644)
|
|
|
|
|
|
def _close_file(file_descriptor):
|
|
os.close(file_descriptor)
|
|
|
|
|
|
def _lock_file(file_descriptor):
|
|
fcntl.flock(file_descriptor, 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 error:
|
|
if elapsed < timeout:
|
|
sleep_time = min(10, timeout - elapsed)
|
|
logging.info(
|
|
'Could not create 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(error))) from error
|
|
|
|
|
|
@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()
|