Note:
This page is primarily intended for developers of Mercurial.
Locking Design
How locking works in Mercurial.
Contents
1. Overview
There are two locks in Mercurial, known as "lock" (for repository data) and "wlock" (for working directory data). Generally speaking, locks are only taken by writers and there is no limit on the number of simultaneous readers. Consistency for readers is managed using atomic operations (like rename) and careful ordering of data visibility.
2. Lock files
Lock files are implemented as symlinks where possible. This allows locks to be created and information about their owner stored in a single operation.
3. The repository lock
The repository lock lives in .hg/store/lock and covers all data in store/. Writes to the repository are ordered as follows:
- write to file revlogs (data, then index)
- write to manifest revlogs (data, then index)
- write to changelog data
- write to temporary changelog index
- atomic rename of changelog index
Thus, if a reader starts at the changelog index and works down the tree in the opposite order, it will always see complete changesets without requiring a lock.
Possible problems can occur if a reader reads files out of order. For instance, copying a repository with other tools (rsync, for instance) during a pull or commit may result in copying a changelog refers to a manifest entry that isn't in the copy.
Note that the repository lock is also taken during a local hardlink clone for similar reasons, even though this is technically a read-only operation.
3.1. Interactions with non-append-only operations
Mercurial's locking is designed with the assumption that all operations are append-only. If data in the repository disappears in the middle of a read operation, problems may occur.
Thus, if Alice does a pull from Bob and Bob does a strip or rollback in the middle, Alice may get a damaged pull. To avoid this, Bob should take his repository offline when running a destructive operation.
4. The working directory lock
The working directory lock (.hg/wlock) protects some of the files in .hg/ including:
- dirstate
- branch
- tags.cache
- branchheads.cache
Note that it does not protect the contents of the working directory itself, as it's impractical to try to stop the user from clobbering their own data.
Again, consistent view of these files is managed with atomic rename operations and readers do not need to acquire a lock. Readers that intend to subsequently write should probably acquire this lock before beginning so that they avoid races with other writers.
This lock should be acquired BEFORE the repository lock to avoid AB-BA deadlocks.
5. Functions
Locks are acquired with the two methods on the localrepository class:
- lock()
- wlock()
The usual pattern for acquiring both locks is:
from lock import release wlock = lock = None try: wlock = repo.wlock() # must come first lock = repo.lock() tr = repo.transaction("foo") # perform write operation finally: release(tr, lock, wlock) # reverse order
or, using the objects as context managers (which ensures that locks are released in the correct, reverse order):
with repo.wlock(): # must come first with repo.lock(): with repo.transaction("foo") as tr: # perform write operation
Locks can be taken recursively.
Atomic files are managed with the atomictemp flag to an opener:
f = repo.opener("somefile", "w", atomictemp=True) # write to f f.close() # finalize the operation