Mercurial > hg
view tests/test-push-checkheads-superceed-A1.t @ 43709:039fbd14d4e2
lock: fix race in lock-breaking code
With low frequency, I see hg pulls fail with output like:
abort: no such file or directory: .hg/store/lock
I think what happens is, in lock.py, in:
def _testlock(self, locker):
if not self._lockshouldbebroken(locker):
return locker
# if locker dead, break lock. must do this with another lock
# held, or can race and break valid lock.
try:
with lock(self.vfs, self.f + b'.break', timeout=0):
self.vfs.unlink(self.f)
except error.LockError:
return locker
if a lock is breakable on disk, and two hg processes concurrently get
to the "if locker dead" comment, a possible interleaving is: process1
finishes executing the function and then process2 finishes executing
the function. If that happens, process2 will either get ENOENT in
self.vfs.unlink (resulting in the spurious failure above), or break a
valid lock and potentially cause repository corruption.
The fix is simple enough: make sure the lock is breakable _inside_ the
critical section, because only then can we know that no other process
can invalidate our knowledge on the lock on disk.
I don't think there are tests for this. I've tested this manually
with:
diff --git a/mercurial/lock.py b/mercurial/lock.py
--- a/mercurial/lock.py
+++ b/mercurial/lock.py
@@ -351,6 +351,8 @@ class lock(object):
if not self._lockshouldbebroken(locker):
return locker
+ import random
+ time.sleep(1. + random.random())
# if locker dead, break lock. must do this with another lock
# held, or can race and break valid lock.
try:
@@ -358,6 +360,7 @@ class lock(object):
self.vfs.unlink(self.f)
except error.LockError:
return locker
+ time.sleep(1)
def testlock(self):
"""return id of locker if lock is valid, else None.
and I see this change of behavior before/after this commit:
$ $hg init repo
$ cd repo
$ ln -s $HOSTNAME/effffffc:987654321 .hg/wlock
$ touch a
$ $hg commit -Am_ & $hg commit -Am _; wait
-abort: No such file or directory: '/tmp/repo/.hg/wlock'
adding a
+warning: ignoring unknown working parent 679a8959a8ca!
+nothing changed
Differential Revision: https://phab.mercurial-scm.org/D7199
author | Valentin Gatien-Baron <valentin.gatienbaron@gmail.com> |
---|---|
date | Mon, 18 Nov 2019 20:10:38 -0800 |
parents | 34a46d48d24e |
children | 5996640fc6fe |
line wrap: on
line source
==================================== Testing head checking code: Case A-1 ==================================== Mercurial checks for the introduction of new heads on push. Evolution comes into play to detect if existing branches on the server are being replaced by some of the new one we push. This case is part of a series of tests checking this behavior. Category A: simple case involving a branch being superceeded by another. TestCase 1: single-changeset branch .. old-state: .. .. * 1 changeset branch .. .. new-state: .. .. * 1 changeset branch succeeding to A .. .. expected-result: .. .. * push allowed .. .. graph-summary: .. .. A ø⇠◔ A' .. |/ .. ● $ . $TESTDIR/testlib/push-checkheads-util.sh Test setup ---------- $ mkdir A1 $ cd A1 $ setuprepos creating basic server and client repo updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd client $ hg up 0 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ mkcommit A1 created new head $ hg debugobsolete `getid "desc(A0)" ` `getid "desc(A1)"` 1 new obsolescence markers obsoleted 1 changesets $ hg log -G --hidden @ f6082bc4ffef (draft): A1 | | x 8aaa48160adc (draft): A0 |/ o 1e4be0697311 (public): root Actual testing -------------- $ hg push pushing to $TESTTMP/A1/server searching for changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files (+1 heads) 1 new obsolescence markers obsoleted 1 changesets $ cd ../..