##// END OF EJS Templates
lock: add internal config to not replace signal handlers while locking...
lock: add internal config to not replace signal handlers while locking signal.signal() is blocked in some WSGI environments, and a horrible warning is sent to the server log. So we need a way to disable it, and I think abusing ui.config is the simplest workaround.

File last commit:

r38157:8c828beb stable
r38157:8c828beb stable
Show More
lock.py
397 lines | 13.1 KiB | text/x-python | PythonLexer
Greg Ward
localrepo: document the locking scheme a little better...
r9309 # lock.py - simple advisory locking scheme for mercurial
mpm@selenic.com
Simply repository locking...
r161 #
Vadim Gelfer
update copyrights.
r2859 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
mpm@selenic.com
Simply repository locking...
r161 #
Martin Geisler
updated license to be explicit about GPL version 2
r8225 # This software may be used and distributed according to the terms of the
Matt Mackall
Update license to GPLv2+
r10263 # GNU General Public License version 2 or any later version.
mpm@selenic.com
Simply repository locking...
r161
Gregory Szorc
lock: use absolute_import
r25956 from __future__ import absolute_import
Siddharth Agarwal
lock: turn prepinherit/reacquire into a single context manager...
r26473 import contextlib
Gregory Szorc
lock: use absolute_import
r25956 import errno
Jun Wu
lock: include Linux pid namespace identifier in prefix...
r30921 import os
Yuya Nishihara
lock: block signal interrupt while making a lock file...
r36717 import signal
Gregory Szorc
lock: use absolute_import
r25956 import socket
import time
Ronny Pfannschmidt
add a deprecation warning for gc based lock releasing
r8113 import warnings
mpm@selenic.com
Simply repository locking...
r161
Boris Feld
lock: add a trylock method handling the timeout and messaging logic...
r35209 from .i18n import _
Gregory Szorc
lock: use absolute_import
r25956 from . import (
Augie Fackler
lock: encode result of gethostname into a bytestring
r31375 encoding,
Gregory Szorc
lock: use absolute_import
r25956 error,
Jun Wu
lock: include Linux pid namespace identifier in prefix...
r30921 pycompat,
Yuya Nishihara
lock: add internal config to not replace signal handlers while locking...
r38157 util,
Yuya Nishihara
procutil: bulk-replace function calls to point to new module
r37138 )
from .utils import (
procutil,
Gregory Szorc
lock: use absolute_import
r25956 )
Jun Wu
lock: move lock._host calculation to a function...
r30920 def _getlockprefix():
"""Return a string which is used to differentiate pid namespaces
It's useful to detect "dead" processes and remove stale locks with
Jun Wu
lock: include Linux pid namespace identifier in prefix...
r30921 confidence. Typically it's just hostname. On modern linux, we include an
extra Linux-specific pid namespace identifier.
Jun Wu
lock: move lock._host calculation to a function...
r30920 """
Yuya Nishihara
py3: replace "if ispy3" by encoding.strtolocal()
r35915 result = encoding.strtolocal(socket.gethostname())
Jun Wu
lock: include Linux pid namespace identifier in prefix...
r30921 if pycompat.sysplatform.startswith('linux'):
try:
result += '/%x' % os.stat('/proc/self/ns/pid').st_ino
except OSError as ex:
if ex.errno not in (errno.ENOENT, errno.EACCES, errno.ENOTDIR):
raise
return result
Jun Wu
lock: move lock._host calculation to a function...
r30920
Yuya Nishihara
lock: block signal interrupt while making a lock file...
r36717 @contextlib.contextmanager
def _delayedinterrupt():
"""Block signal interrupt while doing something critical
This makes sure that the code block wrapped by this context manager won't
be interrupted.
For Windows developers: It appears not possible to guard time.sleep()
from CTRL_C_EVENT, so please don't use time.sleep() to test if this is
working.
"""
assertedsigs = []
blocked = False
orighandlers = {}
def raiseinterrupt(num):
if (num == getattr(signal, 'SIGINT', None) or
num == getattr(signal, 'CTRL_C_EVENT', None)):
raise KeyboardInterrupt
else:
raise error.SignalInterrupt
def catchterm(num, frame):
if blocked:
assertedsigs.append(num)
else:
raiseinterrupt(num)
try:
# save handlers first so they can be restored even if a setup is
# interrupted between signal.signal() and orighandlers[] =.
for name in ['CTRL_C_EVENT', 'SIGINT', 'SIGBREAK', 'SIGHUP', 'SIGTERM']:
num = getattr(signal, name, None)
if num and num not in orighandlers:
orighandlers[num] = signal.getsignal(num)
try:
for num in orighandlers:
signal.signal(num, catchterm)
except ValueError:
pass # in a thread? no luck
blocked = True
yield
finally:
# no simple way to reliably restore all signal handlers because
# any loops, recursive function calls, except blocks, etc. can be
# interrupted. so instead, make catchterm() raise interrupt.
blocked = False
try:
for num, handler in orighandlers.items():
signal.signal(num, handler)
except ValueError:
pass # in a thread?
# re-raise interrupt exception if any, which may be shadowed by a new
# interrupt occurred while re-raising the first one
if assertedsigs:
raiseinterrupt(assertedsigs[0])
Boris Feld
lock: allow to configure when the lock messages are displayed...
r35210 def trylock(ui, vfs, lockname, timeout, warntimeout, *args, **kwargs):
Boris Feld
lock: add a trylock method handling the timeout and messaging logic...
r35209 """return an acquired lock or raise an a LockHeld exception
Boris Feld
lock: allow to configure when the lock messages are displayed...
r35210 This function is responsible to issue warnings and or debug messages about
the held lock while trying to acquires it."""
Boris Feld
lock: add a trylock method handling the timeout and messaging logic...
r35209
def printwarning(printer, locker):
"""issue the usual "waiting on lock" message through any channel"""
# show more details for new-style locks
if ':' in locker:
host, pid = locker.split(":", 1)
Yuya Nishihara
py3: fix formatting of lock error message
r36660 msg = (_("waiting for lock on %s held by process %r on host %r\n")
% (pycompat.bytestr(l.desc), pycompat.bytestr(pid),
pycompat.bytestr(host)))
Boris Feld
lock: add a trylock method handling the timeout and messaging logic...
r35209 else:
Yuya Nishihara
py3: fix formatting of lock error message
r36660 msg = (_("waiting for lock on %s held by %r\n")
% (l.desc, pycompat.bytestr(locker)))
Boris Feld
lock: add a trylock method handling the timeout and messaging logic...
r35209 printer(msg)
l = lock(vfs, lockname, 0, *args, dolock=False, **kwargs)
Boris Feld
lock: allow to configure when the lock messages are displayed...
r35210 debugidx = 0 if (warntimeout and timeout) else -1
Boris Feld
lock: add a trylock method handling the timeout and messaging logic...
r35209 warningidx = 0
if not timeout:
warningidx = -1
Boris Feld
lock: allow to configure when the lock messages are displayed...
r35210 elif warntimeout:
warningidx = warntimeout
Boris Feld
lock: add a trylock method handling the timeout and messaging logic...
r35209
delay = 0
while True:
try:
l._trylock()
break
except error.LockHeld as inst:
Boris Feld
lock: allow to configure when the lock messages are displayed...
r35210 if delay == debugidx:
printwarning(ui.debug, inst.locker)
Boris Feld
lock: add a trylock method handling the timeout and messaging logic...
r35209 if delay == warningidx:
printwarning(ui.warn, inst.locker)
if timeout <= delay:
raise error.LockHeld(errno.ETIMEDOUT, inst.filename,
l.desc, inst.locker)
time.sleep(1)
delay += 1
l.delay = delay
if l.delay:
Boris Feld
lock: allow to configure when the lock messages are displayed...
r35210 if 0 <= warningidx <= l.delay:
Augie Fackler
lock: delay is numeric, use %d for formatting...
r36337 ui.warn(_("got lock after %d seconds\n") % l.delay)
Boris Feld
lock: allow to configure when the lock messages are displayed...
r35210 else:
Augie Fackler
lock: delay is numeric, use %d for formatting...
r36337 ui.debug("got lock after %d seconds\n" % l.delay)
Boris Feld
lock: add a trylock method handling the timeout and messaging logic...
r35209 if l.acquirefn:
l.acquirefn()
return l
Eric Hopper
Convert all classes to new-style classes by deriving them from object.
r1559 class lock(object):
Greg Ward
localrepo: document the locking scheme a little better...
r9309 '''An advisory lock held by one process to control access to a set
of files. Non-cooperating processes or incorrectly written scripts
can ignore Mercurial's locking scheme and stomp all over the
repository, so don't do that.
Typically used via localrepository.lock() to lock the repository
store (.hg/store/) or localrepository.wlock() to lock everything
else under .hg/.'''
Vadim Gelfer
change lock format to let us detect and break stale locks....
r1877 # lock is symlink on platforms that support it, file on others.
# symlink is used because create of directory entry and contents
# are atomic even over nfs.
# old-style lock: symlink to pid
# new-style lock: symlink to hostname:pid
Bryan O'Sullivan
lock.py: cache hostname, but not pid, in case we fork
r4947 _host = None
Pulkit Goyal
lock: don't use 'file' as a variable name...
r37677 def __init__(self, vfs, fname, timeout=-1, releasefn=None, acquirefn=None,
Boris Feld
lock: add a trylock method handling the timeout and messaging logic...
r35209 desc=None, inheritchecker=None, parentlock=None,
Yuya Nishihara
lock: add internal config to not replace signal handlers while locking...
r38157 signalsafe=True, dolock=True):
FUJIWARA Katsunori
lock: take both vfs and lock file path relative to vfs to access via vfs...
r20091 self.vfs = vfs
Pulkit Goyal
lock: don't use 'file' as a variable name...
r37677 self.f = fname
mpm@selenic.com
Simply repository locking...
r161 self.held = 0
Benoit Boissinot
add a timeout when a lock is held (default 1024 sec)...
r1787 self.timeout = timeout
Benoit Boissinot
add a releasefn keyword to lock.lock...
r1530 self.releasefn = releasefn
Siddharth Agarwal
lock: move acquirefn call to inside the lock...
r26321 self.acquirefn = acquirefn
Vadim Gelfer
fix backtrace printed when cannot get lock....
r2016 self.desc = desc
Siddharth Agarwal
lock: add a way to prevent locks from being inherited...
r26498 self._inheritchecker = inheritchecker
Siddharth Agarwal
lock: introduce state to keep track of inheritance...
r26356 self.parentlock = parentlock
self._parentheld = False
self._inherited = False
Yuya Nishihara
lock: add internal config to not replace signal handlers while locking...
r38157 if signalsafe:
self._maybedelayedinterrupt = _delayedinterrupt
else:
self._maybedelayedinterrupt = util.nullcontextmanager
Matt Mackall
lock: change name of release chain
r15589 self.postrelease = []
Siddharth Agarwal
lock: add a wrapper to os.getpid() to make testing easier...
r26383 self.pid = self._getpid()
Boris Feld
lock: add a trylock method handling the timeout and messaging logic...
r35209 if dolock:
self.delay = self.lock()
if self.acquirefn:
self.acquirefn()
mpm@selenic.com
Simply repository locking...
r161
Bryan O'Sullivan
lock: turn a lock into a Python context manager...
r27797 def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, exc_tb):
self.release()
mpm@selenic.com
Simply repository locking...
r161 def __del__(self):
Ronny Pfannschmidt
made repo locks recursive and deprecate refcounting based lock releasing...
r8108 if self.held:
Ronny Pfannschmidt
add a deprecation warning for gc based lock releasing
r8113 warnings.warn("use lock.release instead of del lock",
category=DeprecationWarning,
stacklevel=2)
Ronny Pfannschmidt
made repo locks recursive and deprecate refcounting based lock releasing...
r8108 # ensure the lock will be removed
# even if recursive locking did occur
self.held = 1
mpm@selenic.com
Simply repository locking...
r161 self.release()
Siddharth Agarwal
lock: add a wrapper to os.getpid() to make testing easier...
r26383 def _getpid(self):
Yuya Nishihara
procutil: bulk-replace function calls to point to new module
r37138 # wrapper around procutil.getpid() to make testing easier
return procutil.getpid()
Siddharth Agarwal
lock: add a wrapper to os.getpid() to make testing easier...
r26383
mpm@selenic.com
Simply repository locking...
r161 def lock(self):
Benoit Boissinot
add a timeout when a lock is held (default 1024 sec)...
r1787 timeout = self.timeout
Martin Geisler
check-code: flag 0/1 used as constant Boolean expression
r14494 while True:
mpm@selenic.com
Simply repository locking...
r161 try:
Matt Mackall
lock: make trylock private
r26082 self._trylock()
Mads Kiilerich
localrepo: give a sigh of relief when getting lock after waiting for it...
r20380 return self.timeout - timeout
Gregory Szorc
global: mass rewrite to use modern exception syntax...
r25660 except error.LockHeld as inst:
Benoit Boissinot
add a timeout when a lock is held (default 1024 sec)...
r1787 if timeout != 0:
mpm@selenic.com
Simply repository locking...
r161 time.sleep(1)
Benoit Boissinot
add a timeout when a lock is held (default 1024 sec)...
r1787 if timeout > 0:
timeout -= 1
mpm@selenic.com
Simply repository locking...
r161 continue
Matt Mackall
error: move lock errors...
r7640 raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
inst.locker)
mpm@selenic.com
Whitespace cleanups...
r515
Matt Mackall
lock: make trylock private
r26082 def _trylock(self):
Ronny Pfannschmidt
made repo locks recursive and deprecate refcounting based lock releasing...
r8108 if self.held:
self.held += 1
return
Bryan O'Sullivan
lock.py: cache hostname, but not pid, in case we fork
r4947 if lock._host is None:
Jun Wu
lock: move lock._host calculation to a function...
r30920 lock._host = _getlockprefix()
Augie Fackler
lock: use %d to format integer into a bytestring
r31354 lockname = '%s:%d' % (lock._host, self.pid)
Matt Mackall
lock: loop a finite number of times in trylock (issue4787)...
r26081 retry = 5
while not self.held and retry:
retry -= 1
Vadim Gelfer
change lock format to let us detect and break stale locks....
r1877 try:
Yuya Nishihara
lock: add internal config to not replace signal handlers while locking...
r38157 with self._maybedelayedinterrupt():
Yuya Nishihara
lock: block signal interrupt while making a lock file...
r36717 self.vfs.makelock(lockname, self.f)
self.held = 1
Gregory Szorc
global: mass rewrite to use modern exception syntax...
r25660 except (OSError, IOError) as why:
Vadim Gelfer
change lock format to let us detect and break stale locks....
r1877 if why.errno == errno.EEXIST:
Siddharth Agarwal
lock: recognize parent locks while acquiring...
r26387 locker = self._readlock()
FUJIWARA Katsunori
lock: avoid unintentional lock acquisition at failure of readlock...
r32088 if locker is None:
continue
Siddharth Agarwal
lock: recognize parent locks while acquiring...
r26387 # special case where a parent process holds the lock -- this
# is different from the pid being different because we do
# want the unlock and postrelease functions to be called,
# but the lockfile to not be removed.
if locker == self.parentlock:
self._parentheld = True
self.held = 1
return
locker = self._testlock(locker)
Thomas Arendsen Hein
Don't step into an endless loop when lock file is empty.
r3686 if locker is not None:
Yuya Nishihara
py3: back out c77c925987d7 to store bytes filename in IOError...
r36675 raise error.LockHeld(errno.EAGAIN,
self.vfs.join(self.f), self.desc,
locker)
Vadim Gelfer
change lock format to let us detect and break stale locks....
r1877 else:
Matt Mackall
error: move lock errors...
r7640 raise error.LockUnavailable(why.errno, why.strerror,
why.filename, self.desc)
Vadim Gelfer
change lock format to let us detect and break stale locks....
r1877
FUJIWARA Katsunori
lock: avoid unintentional lock acquisition at failure of readlock...
r32089 if not self.held:
# use empty locker to mean "busy for frequent lock/unlock
# by many processes"
raise error.LockHeld(errno.EAGAIN,
Yuya Nishihara
py3: back out c77c925987d7 to store bytes filename in IOError...
r36675 self.vfs.join(self.f), self.desc, "")
FUJIWARA Katsunori
lock: avoid unintentional lock acquisition at failure of readlock...
r32089
Siddharth Agarwal
lock: factor code to read lock into a separate function...
r26290 def _readlock(self):
"""read lock and return its value
Returns None if no lock exists, pid for old-style locks, and host:pid
for new-style locks.
"""
try:
return self.vfs.readlock(self.f)
except (OSError, IOError) as why:
if why.errno == errno.ENOENT:
return None
raise
Siddharth Agarwal
lock: factor out lock testing into a separate function...
r26291 def _testlock(self, locker):
Siddharth Agarwal
lock: factor code to read lock into a separate function...
r26290 if locker is None:
return None
Benoit Boissinot
use __contains__, index or split instead of str.find...
r2579 try:
host, pid = locker.split(":", 1)
except ValueError:
Vadim Gelfer
change lock format to let us detect and break stale locks....
r1877 return locker
Bryan O'Sullivan
lock.py: cache hostname, but not pid, in case we fork
r4947 if host != lock._host:
Vadim Gelfer
change lock format to let us detect and break stale locks....
r1877 return locker
mpm@selenic.com
Simply repository locking...
r161 try:
Benoit Boissinot
use __contains__, index or split instead of str.find...
r2579 pid = int(pid)
Benoit Boissinot
lock: catch specific exceptions
r9685 except ValueError:
Vadim Gelfer
change lock format to let us detect and break stale locks....
r1877 return locker
Yuya Nishihara
procutil: bulk-replace function calls to point to new module
r37138 if procutil.testpid(pid):
Vadim Gelfer
change lock format to let us detect and break stale locks....
r1877 return locker
# if locker dead, break lock. must do this with another lock
# held, or can race and break valid lock.
try:
FUJIWARA Katsunori
lock: take both vfs and lock file path relative to vfs to access via vfs...
r20091 l = lock(self.vfs, self.f + '.break', timeout=0)
self.vfs.unlink(self.f)
Vadim Gelfer
change lock format to let us detect and break stale locks....
r1877 l.release()
Matt Mackall
error: move lock errors...
r7640 except error.LockError:
Vadim Gelfer
change lock format to let us detect and break stale locks....
r1877 return locker
mpm@selenic.com
Simply repository locking...
r161
Siddharth Agarwal
lock: factor out lock testing into a separate function...
r26291 def testlock(self):
"""return id of locker if lock is valid, else None.
If old-style lock, we cannot tell what machine locker is on.
with new-style lock, if locker is on this machine, we can
see if locker is alive. If locker is on this machine but
not alive, we can safely break lock.
The lock file is only deleted when None is returned.
"""
locker = self._readlock()
return self._testlock(locker)
Siddharth Agarwal
lock: turn prepinherit/reacquire into a single context manager...
r26473 @contextlib.contextmanager
def inherit(self):
"""context for the lock to be inherited by a Mercurial subprocess.
Siddharth Agarwal
lock: add a method to prepare the lock for inheritance...
r26357
Siddharth Agarwal
lock: turn prepinherit/reacquire into a single context manager...
r26473 Yields a string that will be recognized by the lock in the subprocess.
Communicating this string to the subprocess needs to be done separately
-- typically by an environment variable.
Siddharth Agarwal
lock: add a method to prepare the lock for inheritance...
r26357 """
if not self.held:
raise error.LockInheritanceContractViolation(
Siddharth Agarwal
lock: turn prepinherit/reacquire into a single context manager...
r26473 'inherit can only be called while lock is held')
Siddharth Agarwal
lock: add a method to prepare the lock for inheritance...
r26357 if self._inherited:
raise error.LockInheritanceContractViolation(
Siddharth Agarwal
lock: turn prepinherit/reacquire into a single context manager...
r26473 'inherit cannot be called while lock is already inherited')
Siddharth Agarwal
lock: add a way to prevent locks from being inherited...
r26498 if self._inheritchecker is not None:
self._inheritchecker()
Siddharth Agarwal
lock: add a method to prepare the lock for inheritance...
r26357 if self.releasefn:
self.releasefn()
if self._parentheld:
lockname = self.parentlock
else:
Pulkit Goyal
py3: use b"%d" instead of str() to convert integers to bytes...
r37676 lockname = b'%s:%d' % (lock._host, self.pid)
Siddharth Agarwal
lock: add a method to prepare the lock for inheritance...
r26357 self._inherited = True
Siddharth Agarwal
lock: turn prepinherit/reacquire into a single context manager...
r26473 try:
yield lockname
finally:
if self.acquirefn:
self.acquirefn()
self._inherited = False
Siddharth Agarwal
lock: add a method to reacquire the lock after subprocesses exit...
r26358
mpm@selenic.com
Simply repository locking...
r161 def release(self):
Pierre-Yves David
lock: add mechanism to register post release callback
r15583 """release the lock and execute callback function if any
Bryan O'Sullivan
Merge spelling fixes
r17537 If the lock has been acquired multiple times, the actual release is
timeless@mozdev.org
spelling: release
r17510 delayed to the last release call."""
Ronny Pfannschmidt
made repo locks recursive and deprecate refcounting based lock releasing...
r8108 if self.held > 1:
self.held -= 1
Benoit Boissinot
lock: use '==' instead of 'is' for integer equality ('is' may not work)
r9680 elif self.held == 1:
mpm@selenic.com
Simply repository locking...
r161 self.held = 0
Siddharth Agarwal
lock: add a wrapper to os.getpid() to make testing easier...
r26383 if self._getpid() != self.pid:
Bryan O'Sullivan
lock: if we fork, ensure that only the parent releases...
r18907 # we forked, and are not the parent
return
mpm@selenic.com
Fix troubles with clone and exception handling...
r503 try:
Siddharth Agarwal
lock: while releasing, unlink lockfile even if the release function throws...
r23032 if self.releasefn:
self.releasefn()
finally:
Siddharth Agarwal
lock.release: do not unlink inherited locks...
r26359 if not self._parentheld:
try:
self.vfs.unlink(self.f)
except OSError:
pass
Siddharth Agarwal
lock.release: don't call postrelease functions for inherited locks...
r26474 # The postrelease functions typically assume the lock is not held
# at all.
if not self._parentheld:
for callback in self.postrelease:
callback()
Gregory Szorc
lock: clear postrelease hooks list after usage...
r28959 # Prevent double usage and help clear cycles.
self.postrelease = None
mpm@selenic.com
Simply repository locking...
r161
Ronny Pfannschmidt
made repo locks recursive and deprecate refcounting based lock releasing...
r8108 def release(*locks):
for lock in locks:
if lock is not None:
lock.release()