135
|
1 # lock.py - simple locking scheme for mercurial
|
|
2 #
|
|
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
|
|
4 #
|
|
5 # This software may be used and distributed according to the terms of the
|
|
6 # GNU General Public License version 2, incorporated herein by reference.
|
|
7
|
|
8 import util, error
|
|
9 import errno, os, socket, time
|
|
10 import warnings
|
|
11
|
|
12 class lock(object):
|
|
13 # lock is symlink on platforms that support it, file on others.
|
|
14
|
|
15 # symlink is used because create of directory entry and contents
|
|
16 # are atomic even over nfs.
|
|
17
|
|
18 # old-style lock: symlink to pid
|
|
19 # new-style lock: symlink to hostname:pid
|
|
20
|
|
21 _host = None
|
|
22
|
|
23 def __init__(self, file, timeout=-1, releasefn=None, desc=None):
|
|
24 self.f = file
|
|
25 self.held = 0
|
|
26 self.timeout = timeout
|
|
27 self.releasefn = releasefn
|
|
28 self.desc = desc
|
|
29 self.lock()
|
|
30
|
|
31 def __del__(self):
|
|
32 if self.held:
|
|
33 warnings.warn("use lock.release instead of del lock",
|
|
34 category=DeprecationWarning,
|
|
35 stacklevel=2)
|
|
36
|
|
37 # ensure the lock will be removed
|
|
38 # even if recursive locking did occur
|
|
39 self.held = 1
|
|
40
|
|
41 self.release()
|
|
42
|
|
43 def lock(self):
|
|
44 timeout = self.timeout
|
|
45 while 1:
|
|
46 try:
|
|
47 self.trylock()
|
|
48 return 1
|
|
49 except error.LockHeld, inst:
|
|
50 if timeout != 0:
|
|
51 time.sleep(1)
|
|
52 if timeout > 0:
|
|
53 timeout -= 1
|
|
54 continue
|
|
55 raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
|
|
56 inst.locker)
|
|
57
|
|
58 def trylock(self):
|
|
59 if self.held:
|
|
60 self.held += 1
|
|
61 return
|
|
62 if lock._host is None:
|
|
63 lock._host = socket.gethostname()
|
|
64 lockname = '%s:%s' % (lock._host, os.getpid())
|
|
65 while not self.held:
|
|
66 try:
|
|
67 util.makelock(lockname, self.f)
|
|
68 self.held = 1
|
|
69 except (OSError, IOError), why:
|
|
70 if why.errno == errno.EEXIST:
|
|
71 locker = self.testlock()
|
|
72 if locker is not None:
|
|
73 raise error.LockHeld(errno.EAGAIN, self.f, self.desc,
|
|
74 locker)
|
|
75 else:
|
|
76 raise error.LockUnavailable(why.errno, why.strerror,
|
|
77 why.filename, self.desc)
|
|
78
|
|
79 def testlock(self):
|
|
80 """return id of locker if lock is valid, else None.
|
|
81
|
|
82 If old-style lock, we cannot tell what machine locker is on.
|
|
83 with new-style lock, if locker is on this machine, we can
|
|
84 see if locker is alive. If locker is on this machine but
|
|
85 not alive, we can safely break lock.
|
|
86
|
|
87 The lock file is only deleted when None is returned.
|
|
88
|
|
89 """
|
|
90 locker = util.readlock(self.f)
|
|
91 try:
|
|
92 host, pid = locker.split(":", 1)
|
|
93 except ValueError:
|
|
94 return locker
|
|
95 if host != lock._host:
|
|
96 return locker
|
|
97 try:
|
|
98 pid = int(pid)
|
|
99 except:
|
|
100 return locker
|
|
101 if util.testpid(pid):
|
|
102 return locker
|
|
103 # if locker dead, break lock. must do this with another lock
|
|
104 # held, or can race and break valid lock.
|
|
105 try:
|
|
106 l = lock(self.f + '.break')
|
|
107 l.trylock()
|
|
108 os.unlink(self.f)
|
|
109 l.release()
|
|
110 except error.LockError:
|
|
111 return locker
|
|
112
|
|
113 def release(self):
|
|
114 if self.held > 1:
|
|
115 self.held -= 1
|
|
116 elif self.held is 1:
|
|
117 self.held = 0
|
|
118 if self.releasefn:
|
|
119 self.releasefn()
|
|
120 try:
|
|
121 os.unlink(self.f)
|
|
122 except: pass
|
|
123
|
|
124 def release(*locks):
|
|
125 for lock in locks:
|
|
126 if lock is not None:
|
|
127 lock.release()
|
|
128
|