diff --git a/hgext3rd/fbamend/hiddenoverride.py b/hgext3rd/fbamend/hiddenoverride.py --- a/hgext3rd/fbamend/hiddenoverride.py +++ b/hgext3rd/fbamend/hiddenoverride.py @@ -8,7 +8,9 @@ from __future__ import absolute_import import contextlib +import errno import os +import time from mercurial.node import short from mercurial import ( @@ -72,7 +74,15 @@ return result @contextlib.contextmanager -def flock(lockpath): +def flock(lockpath, description, timeout=-1): + """A flock based lock object. Currently it is always non-blocking. + + Note that since it is flock based, you can accidentally take it multiple + times within one process and the first one to be released will release all + of them. So the caller needs to be careful to not create more than one + instance per lock. + """ + # best effort lightweight lock try: import fcntl @@ -80,15 +90,29 @@ except ImportError: # fallback to Mercurial lock vfs = vfsmod.vfs(os.path.dirname(lockpath)) - with lockmod.lock(vfs, os.path.basename(lockpath)): + with lockmod.lock(vfs, os.path.basename(lockpath), timeout=timeout): yield return # make sure lock file exists util.makedirs(os.path.dirname(lockpath)) with open(lockpath, 'a'): pass - lockfd = os.open(lockpath, os.O_RDONLY | os.O_CREAT, 0o664) - fcntl.flock(lockfd, fcntl.LOCK_EX) + lockfd = os.open(lockpath, os.O_RDONLY, 0o664) + start = time.time() + while True: + try: + fcntl.flock(lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB) + break + except IOError as ex: + if ex.errno == errno.EAGAIN: + if timeout != -1 and time.time() - start > timeout: + raise error.LockHeld(errno.EAGAIN, lockpath, description, + '') + else: + time.sleep(0.05) + continue + raise + try: yield finally: @@ -97,7 +121,7 @@ def savepinnednodes(repo, newpin, newunpin, fullargs): # take a narrowed lock so it does not affect repo lock - with flock(repo.svfs.join('obsinhibit.lock')): + with flock(repo.svfs.join('obsinhibit.lock'), 'save pinned nodes'): orignodes = loadpinnednodes(repo) nodes = set(orignodes) nodes |= set(newpin)