/!\ This page is primarily intended for Mercurial's developers.

Locking Design

How locking works in Mercurial.

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:

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:

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:

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


CategoryDeveloper

LockingDesign (last edited 2016-06-29 16:04:09 by MartijnPieters)