lock.py
167 lines
| 5.2 KiB
| text/x-python
|
PythonLexer
/ mercurial / lock.py
Greg Ward
|
r9309 | # lock.py - simple advisory locking scheme for mercurial | ||
mpm@selenic.com
|
r161 | # | ||
Vadim Gelfer
|
r2859 | # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com> | ||
mpm@selenic.com
|
r161 | # | ||
Martin Geisler
|
r8225 | # This software may be used and distributed according to the terms of the | ||
Matt Mackall
|
r10263 | # GNU General Public License version 2 or any later version. | ||
mpm@selenic.com
|
r161 | |||
Gregory Szorc
|
r25956 | from __future__ import absolute_import | ||
import errno | ||||
import os | ||||
import socket | ||||
import time | ||||
Ronny Pfannschmidt
|
r8113 | import warnings | ||
mpm@selenic.com
|
r161 | |||
Gregory Szorc
|
r25956 | from . import ( | ||
error, | ||||
util, | ||||
) | ||||
Eric Hopper
|
r1559 | class lock(object): | ||
Greg Ward
|
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
|
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
|
r4947 | _host = None | ||
FUJIWARA Katsunori
|
r20091 | def __init__(self, vfs, file, timeout=-1, releasefn=None, desc=None): | ||
self.vfs = vfs | ||||
mpm@selenic.com
|
r161 | self.f = file | ||
self.held = 0 | ||||
Benoit Boissinot
|
r1787 | self.timeout = timeout | ||
Benoit Boissinot
|
r1530 | self.releasefn = releasefn | ||
Vadim Gelfer
|
r2016 | self.desc = desc | ||
Matt Mackall
|
r15589 | self.postrelease = [] | ||
Bryan O'Sullivan
|
r18907 | self.pid = os.getpid() | ||
Mads Kiilerich
|
r20380 | self.delay = self.lock() | ||
mpm@selenic.com
|
r161 | |||
def __del__(self): | ||||
Ronny Pfannschmidt
|
r8108 | if self.held: | ||
Ronny Pfannschmidt
|
r8113 | warnings.warn("use lock.release instead of del lock", | ||
category=DeprecationWarning, | ||||
stacklevel=2) | ||||
Ronny Pfannschmidt
|
r8108 | # ensure the lock will be removed | ||
# even if recursive locking did occur | ||||
self.held = 1 | ||||
mpm@selenic.com
|
r161 | self.release() | ||
def lock(self): | ||||
Benoit Boissinot
|
r1787 | timeout = self.timeout | ||
Martin Geisler
|
r14494 | while True: | ||
mpm@selenic.com
|
r161 | try: | ||
Matt Mackall
|
r26082 | self._trylock() | ||
Mads Kiilerich
|
r20380 | return self.timeout - timeout | ||
Gregory Szorc
|
r25660 | except error.LockHeld as inst: | ||
Benoit Boissinot
|
r1787 | if timeout != 0: | ||
mpm@selenic.com
|
r161 | time.sleep(1) | ||
Benoit Boissinot
|
r1787 | if timeout > 0: | ||
timeout -= 1 | ||||
mpm@selenic.com
|
r161 | continue | ||
Matt Mackall
|
r7640 | raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc, | ||
inst.locker) | ||||
mpm@selenic.com
|
r515 | |||
Matt Mackall
|
r26082 | def _trylock(self): | ||
Ronny Pfannschmidt
|
r8108 | if self.held: | ||
self.held += 1 | ||||
return | ||||
Bryan O'Sullivan
|
r4947 | if lock._host is None: | ||
lock._host = socket.gethostname() | ||||
Bryan O'Sullivan
|
r18907 | lockname = '%s:%s' % (lock._host, self.pid) | ||
Matt Mackall
|
r26081 | retry = 5 | ||
while not self.held and retry: | ||||
retry -= 1 | ||||
Vadim Gelfer
|
r1877 | try: | ||
FUJIWARA Katsunori
|
r20091 | self.vfs.makelock(lockname, self.f) | ||
Vadim Gelfer
|
r1877 | self.held = 1 | ||
Gregory Szorc
|
r25660 | except (OSError, IOError) as why: | ||
Vadim Gelfer
|
r1877 | if why.errno == errno.EEXIST: | ||
locker = self.testlock() | ||||
Thomas Arendsen Hein
|
r3686 | if locker is not None: | ||
FUJIWARA Katsunori
|
r20091 | raise error.LockHeld(errno.EAGAIN, | ||
self.vfs.join(self.f), self.desc, | ||||
Matt Mackall
|
r7640 | locker) | ||
Vadim Gelfer
|
r1877 | else: | ||
Matt Mackall
|
r7640 | raise error.LockUnavailable(why.errno, why.strerror, | ||
why.filename, self.desc) | ||||
Vadim Gelfer
|
r1877 | |||
def testlock(self): | ||||
Thomas Arendsen Hein
|
r3686 | """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. | ||||
""" | ||||
Tomasz Kleczek
|
r17682 | try: | ||
FUJIWARA Katsunori
|
r20091 | locker = self.vfs.readlock(self.f) | ||
Gregory Szorc
|
r25660 | except (OSError, IOError) as why: | ||
Tomasz Kleczek
|
r17682 | if why.errno == errno.ENOENT: | ||
return None | ||||
raise | ||||
Benoit Boissinot
|
r2579 | try: | ||
host, pid = locker.split(":", 1) | ||||
except ValueError: | ||||
Vadim Gelfer
|
r1877 | return locker | ||
Bryan O'Sullivan
|
r4947 | if host != lock._host: | ||
Vadim Gelfer
|
r1877 | return locker | ||
mpm@selenic.com
|
r161 | try: | ||
Benoit Boissinot
|
r2579 | pid = int(pid) | ||
Benoit Boissinot
|
r9685 | except ValueError: | ||
Vadim Gelfer
|
r1877 | return locker | ||
if util.testpid(pid): | ||||
return locker | ||||
# if locker dead, break lock. must do this with another lock | ||||
# held, or can race and break valid lock. | ||||
try: | ||||
FUJIWARA Katsunori
|
r20091 | l = lock(self.vfs, self.f + '.break', timeout=0) | ||
self.vfs.unlink(self.f) | ||||
Vadim Gelfer
|
r1877 | l.release() | ||
Matt Mackall
|
r7640 | except error.LockError: | ||
Vadim Gelfer
|
r1877 | return locker | ||
mpm@selenic.com
|
r161 | |||
def release(self): | ||||
Pierre-Yves David
|
r15583 | """release the lock and execute callback function if any | ||
Bryan O'Sullivan
|
r17537 | If the lock has been acquired multiple times, the actual release is | ||
timeless@mozdev.org
|
r17510 | delayed to the last release call.""" | ||
Ronny Pfannschmidt
|
r8108 | if self.held > 1: | ||
self.held -= 1 | ||||
Benoit Boissinot
|
r9680 | elif self.held == 1: | ||
mpm@selenic.com
|
r161 | self.held = 0 | ||
Bryan O'Sullivan
|
r18907 | if os.getpid() != self.pid: | ||
# we forked, and are not the parent | ||||
return | ||||
mpm@selenic.com
|
r503 | try: | ||
Siddharth Agarwal
|
r23032 | if self.releasefn: | ||
self.releasefn() | ||||
finally: | ||||
try: | ||||
self.vfs.unlink(self.f) | ||||
except OSError: | ||||
pass | ||||
Matt Mackall
|
r15589 | for callback in self.postrelease: | ||
Pierre-Yves David
|
r15583 | callback() | ||
mpm@selenic.com
|
r161 | |||
Ronny Pfannschmidt
|
r8108 | def release(*locks): | ||
for lock in locks: | ||||
if lock is not None: | ||||
lock.release() | ||||