##// END OF EJS Templates
interfaces: make all methods on any repository Protocol class abstract...
interfaces: make all methods on any repository Protocol class abstract These were converted to Protocol classes before it was known there would be a benefit to making them abstract. See cdd4bc69bfc1 and db6efd74cf14. The one exception here is the `peer` class, which is meant to provide a default implementation, not be a standalone interface.

File last commit:

r52756:f4733654 default
r53396:9358d786 default
Show More
lock.py
409 lines | 12.3 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 #
Raphaël Gomès
contributor: change mentions of mpm to olivia...
r47575 # Copyright 2005, 2006 Olivia Mackall <olivia@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
Matt Harbison
typing: add `from __future__ import annotations` to most files...
r52756 from __future__ import annotations
Gregory Szorc
lock: use absolute_import
r25956
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
lock: properly convert error to bytes...
r52179 import typing
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
Manuel Jacob
py3: catch specific OSError subclasses instead of checking errno...
r50205 except (FileNotFoundError, PermissionError, NotADirectoryError):
pass
Jun Wu
lock: include Linux pid namespace identifier in prefix...
r30921 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 [
safehasattr: pass attribute name as string instead of bytes...
r51480 'CTRL_C_EVENT',
'SIGINT',
'SIGBREAK',
'SIGHUP',
'SIGTERM',
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ]:
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
Matt Harbison
typing: lock in correct changes from pytype 2023.04.11 -> 2023.06.16...
r52708 def trylock(ui, vfs, lockname, timeout, warntimeout, *args, **kwargs) -> "lock":
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."""
test-lock: use synchronisation file instead of sleep...
r52390 devel_wait_file = kwargs.pop("devel_wait_sync_file", None)
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:
test-lock: use synchronisation file instead of sleep...
r52390 if devel_wait_file is not None:
# create the file to signal we are waiting
with open(devel_wait_file, 'w'):
pass
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:
lock: properly convert error to bytes...
r52179 assert isinstance(inst.filename, bytes)
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.LockHeld(
lock: properly convert error to bytes...
r52179 errno.ETIMEDOUT,
typing.cast(bytes, inst.filename),
l.desc,
inst.locker,
Augie Fackler
formatting: blacken the codebase...
r43346 )
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
Gregory Szorc
py3: use class X: instead of class X(object):...
r49801 class lock:
Augie Fackler
formating: upgrade to black 20.8b1...
r46554 """An advisory lock held by one process to control access to a set
Greg Ward
localrepo: document the locking scheme a little better...
r9309 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
Augie Fackler
formating: upgrade to black 20.8b1...
r46554 else under .hg/."""
Greg Ward
localrepo: document the locking scheme a little better...
r9309
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,
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
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):
Kyle Lippincott
lock: pass "success" boolean to _afterlock callbacks...
r44217 success = all(a is None for a in (exc_type, exc_value, exc_tb))
self.release(success=success)
Bryan O'Sullivan
lock: turn a lock into a Python context manager...
r27797
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(
Augie Fackler
cleanup: remove pointless r-prefixes on double-quoted strings...
r43809 "use lock.release instead of del lock",
Augie Fackler
formatting: blacken the codebase...
r43346 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
Matt Harbison
typing: lock in correct changes from pytype 2023.04.11 -> 2023.06.16...
r52708 def lock(self) -> int:
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 Harbison
typing: lock in correct changes from pytype 2023.04.11 -> 2023.06.16...
r52708 def _trylock(self) -> None:
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 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:
lock: properly convert error to bytes...
r52179 assert isinstance(why.filename, bytes)
assert isinstance(why.strerror, str)
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.LockUnavailable(
lock: properly convert error to bytes...
r52179 why.errno,
why.strerror,
typing.cast(bytes, why.filename),
self.desc,
Augie Fackler
formatting: blacken the codebase...
r43346 )
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)
Manuel Jacob
py3: catch FileNotFoundError instead of checking errno == ENOENT
r50201 except FileNotFoundError:
return None
Siddharth Agarwal
lock: factor code to read lock into a separate function...
r26290
Valentin Gatien-Baron
lock: refactor in preparation for next commit...
r44107 def _lockshouldbebroken(self, locker):
Siddharth Agarwal
lock: factor code to read lock into a separate function...
r26290 if locker is None:
Valentin Gatien-Baron
lock: refactor in preparation for next commit...
r44107 return False
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:
Valentin Gatien-Baron
lock: refactor in preparation for next commit...
r44107 return False
Bryan O'Sullivan
lock.py: cache hostname, but not pid, in case we fork
r4947 if host != lock._host:
Valentin Gatien-Baron
lock: refactor in preparation for next commit...
r44107 return False
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:
Valentin Gatien-Baron
lock: refactor in preparation for next commit...
r44107 return False
Yuya Nishihara
procutil: bulk-replace function calls to point to new module
r37138 if procutil.testpid(pid):
Valentin Gatien-Baron
lock: refactor in preparation for next commit...
r44107 return False
return True
def _testlock(self, locker):
if not self._lockshouldbebroken(locker):
Vadim Gelfer
change lock format to let us detect and break stale locks....
r1877 return locker
Valentin Gatien-Baron
lock: refactor in preparation for next commit...
r44107
Vadim Gelfer
change lock format to let us detect and break stale locks....
r1877 # if locker dead, break lock. must do this with another lock
# held, or can race and break valid lock.
try:
Valentin Gatien-Baron
lock: refactor in preparation for next commit...
r44107 with lock(self.vfs, self.f + b'.break', timeout=0):
Valentin Gatien-Baron
lock: fix race in lock-breaking code...
r44108 locker = self._readlock()
if not self._lockshouldbebroken(locker):
return locker
Valentin Gatien-Baron
lock: refactor in preparation for next commit...
r44107 self.vfs.unlink(self.f)
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)
Kyle Lippincott
lock: pass "success" boolean to _afterlock callbacks...
r44217 def release(self, success=True):
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:
Martin von Zweigbergk
locking: remove support for inheriting locks in subprocess...
r46092 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.
Martin von Zweigbergk
locking: remove support for inheriting locks in subprocess...
r46092 for callback in self.postrelease:
callback(success)
# 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()