Implementation:Iterative Dvc Lock
| Knowledge Sources | |
|---|---|
| Domains | Concurrency, Process_Management |
| Last Updated | 2026-02-10 10:00 GMT |
Overview
dvc/lock.py (222 lines) implements process-level locking for DVC repositories. It defines an abstract base class LockBase, three concrete implementations (LockNoop, Lock, HardlinkLock), and a factory function make_lock(). This prevents concurrent DVC processes from corrupting repository state.
from dvc.lock import make_lock, Lock, HardlinkLock
Source File
| Property | Value |
|---|---|
| File | dvc/lock.py
|
| Lines | 222 |
| Classes | LockBase, LockNoop, Lock, HardlinkLock
|
| Factory | make_lock()
|
Constants
| Name | Value | Description |
|---|---|---|
DEFAULT_TIMEOUT |
3 |
Default timeout in seconds for lock acquisition |
FAILED_TO_LOCK_MESSAGE |
(string) | User-facing error message with link to DVC troubleshooting docs |
Exception: LockError
class LockError(DvcException):
"""Thrown when unable to acquire the lock for DVC repo."""
Raised when a lock cannot be acquired after the configured timeout or retry attempts.
Class: LockBase (ABC)
LockBase is an abstract base class defining the interface for all lock implementations.
Abstract Methods
| Method | Description |
|---|---|
__init__(self, lockfile) |
Stores the lockfile path |
lock(self) |
Acquires the lock |
unlock(self) |
Releases the lock |
is_locked (property) |
Returns whether the lock is currently held |
__enter__(self) |
Context manager entry |
__exit__(self, typ, value, tbck) |
Context manager exit |
Class: LockNoop
LockNoop is a no-op lock implementation that uses a simple boolean flag. It is useful for testing or scenarios where locking is not required.
class LockNoop(LockBase):
def __init__(self, *args, **kwargs):
self._lock = False
The unlock() method raises DvcException if called when the lock is not held, maintaining correct lock semantics even in the no-op case.
Class: Lock
Lock is the primary lock implementation, backed by zc.lockfile.
Constructor
def __init__(self, lockfile, friendly=False, wait=False, **kwargs)
| Parameter | Description |
|---|---|
lockfile |
Path to the lock file |
friendly |
If True, displays a progress bar while waiting
|
wait |
If True, waits indefinitely; otherwise retries up to 6 times
|
Lock Acquisition
The lock() method implements a retry loop:
- Calculates a delay of
DEFAULT_TIMEOUT / 6(0.5 seconds) between retries - If
wait=True, retries indefinitely; otherwise gives up after 6 attempts - Displays a Tqdm progress bar when
friendly=True, with a message directing users to hardlink_lock documentation - Catches
zc.lockfile.LockErrorand re-raises asLockErrorwith the troubleshooting message
Unlock
The unlock() method:
- Returns silently if the lock acquisition previously failed (graceful degradation)
- Raises
DvcExceptionif called on an unlocked lock - Calls
self._lock.close()on the underlyingzc.lockfile.LockFile
Class: HardlinkLock
HardlinkLock extends both flufl.lock.Lock and LockBase. It uses hardlink-based locking, which is more reliable on certain filesystems (especially NFS).
Constructor
def __init__(self, lockfile, tmp_dir=None, wait=False, **kwargs)
Key implementation details:
- Uses
socket.gethostname()instead ofsocket.getfqdn()for performance (FQDN resolution can take ~5 seconds) - Sets a lock lifetime of 365 days (effectively permanent)
- Supports optional
tmp_dirfor storing claim files
Lock Acquisition
The lock() method wraps flufl.lock.Lock.lock() with:
- A default timeout of
DEFAULT_TIMEOUTseconds (unlesswait=True) - A Tqdm progress bar when both
waitandfriendlyare enabled - Conversion of
flufl.lock.TimeOutErrortoLockError
Claim File Handling
The _set_claimfile() method overrides the parent to hash the claim file path using MD5 on Windows, where file path length limitations may cause issues.
Factory Function: make_lock
def make_lock(lockfile, tmp_dir=None, friendly=False, hardlink_lock=False, wait=False)
Returns either a HardlinkLock or Lock instance based on the hardlink_lock flag. This is the primary entry point for creating lock objects in DVC.
| Parameter | Default | Description |
|---|---|---|
lockfile |
(required) | Path to the lock file |
tmp_dir |
None |
Temp directory for claim files (HardlinkLock only) |
friendly |
False |
Show user-facing progress while waiting |
hardlink_lock |
False |
Use hardlink-based locking |
wait |
False |
Wait indefinitely for the lock |
Class Hierarchy
LockBase (ABC)
+-- LockNoop
+-- Lock (zc.lockfile backend)
+-- HardlinkLock (flufl.lock + LockBase)
Key Dependencies
| Module | Usage |
|---|---|
zc.lockfile |
Backend for the standard Lock class
|
flufl.lock |
Backend for HardlinkLock class
|
dvc.progress.Tqdm |
Progress display while waiting for lock |
dvc.exceptions.DvcException |
Base exception class |
dvc.utils.format_link |
Formatting documentation URLs in error messages |
See Also
- Implementation:Progress -- Tqdm progress bar used during lock waiting
- Implementation:Repo_Commit -- Uses the
@lockeddecorator that relies on this locking mechanism