-
Notifications
You must be signed in to change notification settings - Fork 1.3k
dvc: switch to flufl lock #2461
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -3,58 +3,118 @@ | |||||
| from __future__ import unicode_literals | ||||||
|
|
||||||
| import os | ||||||
| import hashlib | ||||||
| import time | ||||||
| import zc.lockfile | ||||||
|
|
||||||
| from dvc.exceptions import DvcException | ||||||
| from datetime import timedelta | ||||||
|
|
||||||
| from funcy.py3 import lkeep | ||||||
|
|
||||||
| class LockError(DvcException): | ||||||
| """Thrown when unable to acquire the lock for dvc repo.""" | ||||||
| from dvc.exceptions import DvcException | ||||||
| from dvc.utils import makedirs | ||||||
| from dvc.utils.compat import is_py3 | ||||||
|
|
||||||
|
|
||||||
| class Lock(object): | ||||||
| """Class for dvc repo lock. | ||||||
| DEFAULT_TIMEOUT = 5 | ||||||
|
|
||||||
| Args: | ||||||
| dvc_dir (str): path to the directory that the lock should be created | ||||||
| in. | ||||||
| name (str): name of the lock file. | ||||||
| """ | ||||||
|
|
||||||
| LOCK_FILE = "lock" | ||||||
| TIMEOUT = 5 | ||||||
| class LockError(DvcException): | ||||||
| """Thrown when unable to acquire the lock for dvc repo.""" | ||||||
|
|
||||||
| def __init__(self, dvc_dir, name=LOCK_FILE): | ||||||
| self.lock_file = os.path.join(dvc_dir, name) | ||||||
| self._lock = None | ||||||
|
|
||||||
| def _do_lock(self): | ||||||
| try: | ||||||
| self._lock = zc.lockfile.LockFile(self.lock_file) | ||||||
| except zc.lockfile.LockError: | ||||||
| raise LockError( | ||||||
| "cannot perform the cmd since DVC is busy and " | ||||||
| "locked. Please retry the cmd later." | ||||||
| ) | ||||||
| if is_py3: | ||||||
| import flufl.lock | ||||||
|
|
||||||
| class Lock(flufl.lock.Lock): | ||||||
| """Class for dvc repo lock. | ||||||
| Args: | ||||||
| lockfile (str): the lock filename | ||||||
| in. | ||||||
| lifetime (int | timedelta): hold the lock for so long. | ||||||
| tmp_dir (str): a directory to store claim files. | ||||||
| """ | ||||||
|
|
||||||
| def __init__(self, lockfile, lifetime=None, tmp_dir=None): | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for setting a long default lifetime. Don't see this one used anywhere now
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the record: will remove this myself right after merge to move along faster. |
||||||
| if lifetime is None: | ||||||
| lifetime = timedelta(days=365) # Lock for good by default | ||||||
| elif isinstance(lifetime, int): | ||||||
| lifetime = timedelta(seconds=lifetime) | ||||||
|
|
||||||
| self._tmp_dir = tmp_dir | ||||||
| if self._tmp_dir is not None: | ||||||
| makedirs(self._tmp_dir, exist_ok=True) | ||||||
|
|
||||||
| super(Lock, self).__init__(lockfile, lifetime=lifetime) | ||||||
|
|
||||||
| @property | ||||||
| def lockfile(self): | ||||||
| return self._lockfile | ||||||
|
|
||||||
| @property | ||||||
| def files(self): | ||||||
| return lkeep([self._lockfile, self._tmp_dir]) | ||||||
|
|
||||||
| def lock(self): | ||||||
| try: | ||||||
| super(Lock, self).lock(timedelta(seconds=DEFAULT_TIMEOUT)) | ||||||
| except flufl.lock.TimeOutError: | ||||||
| raise LockError( | ||||||
| "cannot perform the cmd since DVC is busy and " | ||||||
| "locked. Please retry the cmd later." | ||||||
| ) | ||||||
|
|
||||||
| def _set_claimfile(self, pid=None): | ||||||
| super(Lock, self)._set_claimfile(pid) | ||||||
|
|
||||||
| if self._tmp_dir is not None: | ||||||
| # Under Windows file path length is limited so we hash it | ||||||
| filename = hashlib.md5(self._claimfile.encode()).hexdigest() | ||||||
| self._claimfile = os.path.join( | ||||||
| self._tmp_dir, filename + ".lock" | ||||||
| ) | ||||||
|
|
||||||
|
|
||||||
| else: | ||||||
| import zc.lockfile | ||||||
|
|
||||||
| class Lock(object): | ||||||
| """Class for dvc repo lock. | ||||||
| Uses zc.lockfile as backend. | ||||||
| """ | ||||||
|
|
||||||
| def __init__(self, lockfile, lifetime=None, tmp_dir=None): | ||||||
| self.lockfile = lockfile | ||||||
| self._lock = None | ||||||
|
|
||||||
| @property | ||||||
| def files(self): | ||||||
| return [self.lockfile] | ||||||
|
|
||||||
| def _do_lock(self): | ||||||
| try: | ||||||
| self._lock = zc.lockfile.LockFile(self.lockfile) | ||||||
| except zc.lockfile.LockError: | ||||||
| raise LockError( | ||||||
| "cannot perform the cmd since DVC is busy and " | ||||||
| "locked. Please retry the cmd later." | ||||||
| ) | ||||||
|
|
||||||
| def lock(self): | ||||||
| try: | ||||||
| self._do_lock() | ||||||
| return | ||||||
| except LockError: | ||||||
| time.sleep(DEFAULT_TIMEOUT) | ||||||
|
|
||||||
| def lock(self): | ||||||
| """Acquire lock for dvc repo.""" | ||||||
| try: | ||||||
| self._do_lock() | ||||||
| return | ||||||
| except LockError: | ||||||
| time.sleep(self.TIMEOUT) | ||||||
|
|
||||||
| self._do_lock() | ||||||
|
|
||||||
| def unlock(self): | ||||||
| """Release lock for dvc repo.""" | ||||||
| self._lock.close() | ||||||
| self._lock = None | ||||||
| def unlock(self): | ||||||
| self._lock.close() | ||||||
| self._lock = None | ||||||
|
|
||||||
| def __enter__(self): | ||||||
| self.lock() | ||||||
| def __enter__(self): | ||||||
| self.lock() | ||||||
|
|
||||||
| def __exit__(self, typ, value, tbck): | ||||||
| self.unlock() | ||||||
| def __exit__(self, typ, value, tbck): | ||||||
| self.unlock() | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,7 +25,9 @@ class Updater(object): # pragma: no cover | |
| def __init__(self, dvc_dir): | ||
| self.dvc_dir = dvc_dir | ||
| self.updater_file = os.path.join(dvc_dir, self.UPDATER_FILE) | ||
| self.lock = Lock(dvc_dir, self.updater_file + ".lock") | ||
| self.lock = Lock( | ||
|
||
| self.updater_file + ".lock", tmp_dir=os.path.join(dvc_dir, "tmp") | ||
| ) | ||
| self.current = parse_version(__version__).base_version | ||
|
|
||
| def _is_outdated_file(self): | ||
|
|
@@ -41,7 +43,7 @@ def _with_lock(self, func, action): | |
| func() | ||
| except LockError: | ||
| msg = "Failed to acquire '{}' before {} updates" | ||
| logger.debug(msg.format(self.lock.lock_file, action)) | ||
| logger.debug(msg.format(self.lock.lockfile, action)) | ||
efiop marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| def check(self): | ||
| if os.getenv("CI") or os.getenv("DVC_TEST"): | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.