##// END OF EJS Templates
py3: stop implicitly importing unicode...
py3: stop implicitly importing unicode We should be pycompat.unicode everywhere. It turns out we were doing this everywhere except for one place in templatefilters! Differential Revision: https://phab.mercurial-scm.org/D7006

File last commit:

r43347:687b865b default
r43356:bbcbb82e default
Show More
lock.py
435 lines | 13.4 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 )
Augie Fackler
formatting: blacken the codebase...
r43346 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())
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if pycompat.sysplatform.startswith(b'linux'):
Jun Wu
lock: include Linux pid namespace identifier in prefix...
r30921 try:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 result += b'/%x' % os.stat(b'/proc/self/ns/pid').st_ino
Jun Wu
lock: include Linux pid namespace identifier in prefix...
r30921 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
Augie Fackler
formatting: blacken the codebase...
r43346
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):
Augie Fackler
formatting: blacken the codebase...
r43346 if num == getattr(signal, 'SIGINT', None) or num == getattr(
signal, 'CTRL_C_EVENT', None
):
Yuya Nishihara
lock: block signal interrupt while making a lock file...
r36717 raise KeyboardInterrupt
else:
raise error.SignalInterrupt
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
lock: block signal interrupt while making a lock file...
r36717 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[] =.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 for name in [
b'CTRL_C_EVENT',
b'SIGINT',
b'SIGBREAK',
b'SIGHUP',
b'SIGTERM',
]:
Yuya Nishihara
lock: block signal interrupt while making a lock file...
r36717 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:
Augie Fackler
formatting: blacken the codebase...
r43346 pass # in a thread? no luck
Yuya Nishihara
lock: block signal interrupt while making a lock file...
r36717
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:
Augie Fackler
formatting: blacken the codebase...
r43346 pass # in a thread?
Yuya Nishihara
lock: block signal interrupt while making a lock file...
r36717
# 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])
Augie Fackler
formatting: blacken the codebase...
r43346
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
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if b':' in locker:
host, pid = locker.split(b":", 1)
Augie Fackler
formatting: blacken the codebase...
r43346 msg = _(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b"waiting for lock on %s held by process %r on host %r\n"
Augie Fackler
formatting: blacken the codebase...
r43346 ) % (
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:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 msg = _(b"waiting for lock on %s held by %r\n") % (
Augie Fackler
formatting: blacken the codebase...
r43346 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:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.LockHeld(
errno.ETIMEDOUT, inst.filename, l.desc, inst.locker
)
Boris Feld
lock: add a trylock method handling the timeout and messaging logic...
r35209 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
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.warn(_(b"got lock after %d seconds\n") % l.delay)
Boris Feld
lock: allow to configure when the lock messages are displayed...
r35210 else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.debug(b"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
Augie Fackler
formatting: blacken the codebase...
r43346
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
Augie Fackler
formatting: blacken the codebase...
r43346 def __init__(
self,
vfs,
fname,
timeout=-1,
releasefn=None,
acquirefn=None,
desc=None,
inheritchecker=None,
parentlock=None,
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
Augie Fackler
formatting: blacken the codebase...
r43346 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:
Augie Fackler
formatting: blacken the codebase...
r43346 warnings.warn(
r"use lock.release instead of del lock",
category=DeprecationWarning,
stacklevel=2,
)
Ronny Pfannschmidt
add a deprecation warning for gc based lock releasing
r8113
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
Augie Fackler
formatting: blacken the codebase...
r43346 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
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 lockname = b'%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:
Augie Fackler
formatting: blacken the codebase...
r43346 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:
Augie Fackler
formatting: blacken the codebase...
r43346 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"
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.LockHeld(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 errno.EAGAIN, self.vfs.join(self.f), self.desc, b""
Augie Fackler
formatting: blacken the codebase...
r43346 )
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:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 host, pid = locker.split(b":", 1)
Benoit Boissinot
use __contains__, index or split instead of str.find...
r2579 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:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 l = lock(self.vfs, self.f + b'.break', timeout=0)
FUJIWARA Katsunori
lock: take both vfs and lock file path relative to vfs to access via vfs...
r20091 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(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'inherit can only be called while lock is held'
Augie Fackler
formatting: blacken the codebase...
r43346 )
Siddharth Agarwal
lock: add a method to prepare the lock for inheritance...
r26357 if self._inherited:
raise error.LockInheritanceContractViolation(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'inherit cannot be called while lock is already inherited'
Augie Fackler
formatting: blacken the codebase...
r43346 )
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
Augie Fackler
formatting: blacken the codebase...
r43346
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()