comparison mercurial/lock.py @ 1877:d314a89fa4f1

change lock format to let us detect and break stale locks. old style: symlink to pid new style: symlink to hostname:pid if lock code finds new-style lock, it breaks lock if locking pid is on same machine and pid is not alive. otherwise, lock is left alone. this makes locking code safe with old-style locks and with locks on other machines. new code makes server part of mercurial more robust in case machine crashes, power fails, or crazy user does kill -9.
author Vadim Gelfer <vadim.gelfer@gmail.com>
date Fri, 10 Mar 2006 08:31:31 -0800
parents cd5c1db2132a
children ff5c9a92f556
comparison
equal deleted inserted replaced
1876:2e0fd78587bd 1877:d314a89fa4f1
4 # 4 #
5 # This software may be used and distributed according to the terms 5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference. 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 from demandload import * 8 from demandload import *
9 demandload(globals(), 'errno os time util') 9 demandload(globals(), 'errno os socket time util')
10 10
11 class LockException(Exception): 11 class LockException(Exception):
12 pass 12 pass
13 class LockHeld(LockException): 13 class LockHeld(LockException):
14 pass 14 pass
15 class LockUnavailable(LockException): 15 class LockUnavailable(LockException):
16 pass 16 pass
17 17
18 class lock(object): 18 class lock(object):
19 # lock is symlink on platforms that support it, file on others.
20
21 # symlink is used because create of directory entry and contents
22 # are atomic even over nfs.
23
24 # old-style lock: symlink to pid
25 # new-style lock: symlink to hostname:pid
26
19 def __init__(self, file, timeout=-1, releasefn=None): 27 def __init__(self, file, timeout=-1, releasefn=None):
20 self.f = file 28 self.f = file
21 self.held = 0 29 self.held = 0
22 self.timeout = timeout 30 self.timeout = timeout
23 self.releasefn = releasefn 31 self.releasefn = releasefn
32 self.id = None
33 self.host = None
34 self.pid = None
24 self.lock() 35 self.lock()
25 36
26 def __del__(self): 37 def __del__(self):
27 self.release() 38 self.release()
28 39
39 timeout -= 1 50 timeout -= 1
40 continue 51 continue
41 raise inst 52 raise inst
42 53
43 def trylock(self): 54 def trylock(self):
44 pid = os.getpid() 55 if self.id is None:
56 self.host = socket.gethostname()
57 self.pid = os.getpid()
58 self.id = '%s:%s' % (self.host, self.pid)
59 while not self.held:
60 try:
61 util.makelock(self.id, self.f)
62 self.held = 1
63 except (OSError, IOError), why:
64 if why.errno == errno.EEXIST:
65 locker = self.testlock()
66 if locker:
67 raise LockHeld(locker)
68 else:
69 raise LockUnavailable(why)
70
71 def testlock(self):
72 '''return id of locker if lock is valid, else None.'''
73 # if old-style lock, we cannot tell what machine locker is on.
74 # with new-style lock, if locker is on this machine, we can
75 # see if locker is alive. if locker is on this machine but
76 # not alive, we can safely break lock.
77 locker = util.readlock(self.f)
78 c = locker.find(':')
79 if c == -1:
80 return locker
81 host = locker[:c]
82 if host != self.host:
83 return locker
45 try: 84 try:
46 util.makelock(str(pid), self.f) 85 pid = int(locker[c+1:])
47 self.held = 1 86 except:
48 except (OSError, IOError), why: 87 return locker
49 if why.errno == errno.EEXIST: 88 if util.testpid(pid):
50 raise LockHeld(util.readlock(self.f)) 89 return locker
51 else: 90 # if locker dead, break lock. must do this with another lock
52 raise LockUnavailable(why) 91 # held, or can race and break valid lock.
92 try:
93 l = lock(self.f + '.break')
94 l.trylock()
95 os.unlink(self.f)
96 l.release()
97 except (LockHeld, LockUnavailable):
98 return locker
53 99
54 def release(self): 100 def release(self):
55 if self.held: 101 if self.held:
56 self.held = 0 102 self.held = 0
57 if self.releasefn: 103 if self.releasefn: