##// END OF EJS Templates
lock: factor out lock testing into a separate function...
Siddharth Agarwal -
r26291:1d33842c default
parent child Browse files
Show More
@@ -1,177 +1,180 b''
1 # lock.py - simple advisory locking scheme for mercurial
1 # lock.py - simple advisory locking scheme for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import os
11 import os
12 import socket
12 import socket
13 import time
13 import time
14 import warnings
14 import warnings
15
15
16 from . import (
16 from . import (
17 error,
17 error,
18 util,
18 util,
19 )
19 )
20
20
21 class lock(object):
21 class lock(object):
22 '''An advisory lock held by one process to control access to a set
22 '''An advisory lock held by one process to control access to a set
23 of files. Non-cooperating processes or incorrectly written scripts
23 of files. Non-cooperating processes or incorrectly written scripts
24 can ignore Mercurial's locking scheme and stomp all over the
24 can ignore Mercurial's locking scheme and stomp all over the
25 repository, so don't do that.
25 repository, so don't do that.
26
26
27 Typically used via localrepository.lock() to lock the repository
27 Typically used via localrepository.lock() to lock the repository
28 store (.hg/store/) or localrepository.wlock() to lock everything
28 store (.hg/store/) or localrepository.wlock() to lock everything
29 else under .hg/.'''
29 else under .hg/.'''
30
30
31 # lock is symlink on platforms that support it, file on others.
31 # lock is symlink on platforms that support it, file on others.
32
32
33 # symlink is used because create of directory entry and contents
33 # symlink is used because create of directory entry and contents
34 # are atomic even over nfs.
34 # are atomic even over nfs.
35
35
36 # old-style lock: symlink to pid
36 # old-style lock: symlink to pid
37 # new-style lock: symlink to hostname:pid
37 # new-style lock: symlink to hostname:pid
38
38
39 _host = None
39 _host = None
40
40
41 def __init__(self, vfs, file, timeout=-1, releasefn=None, desc=None):
41 def __init__(self, vfs, file, timeout=-1, releasefn=None, desc=None):
42 self.vfs = vfs
42 self.vfs = vfs
43 self.f = file
43 self.f = file
44 self.held = 0
44 self.held = 0
45 self.timeout = timeout
45 self.timeout = timeout
46 self.releasefn = releasefn
46 self.releasefn = releasefn
47 self.desc = desc
47 self.desc = desc
48 self.postrelease = []
48 self.postrelease = []
49 self.pid = os.getpid()
49 self.pid = os.getpid()
50 self.delay = self.lock()
50 self.delay = self.lock()
51
51
52 def __del__(self):
52 def __del__(self):
53 if self.held:
53 if self.held:
54 warnings.warn("use lock.release instead of del lock",
54 warnings.warn("use lock.release instead of del lock",
55 category=DeprecationWarning,
55 category=DeprecationWarning,
56 stacklevel=2)
56 stacklevel=2)
57
57
58 # ensure the lock will be removed
58 # ensure the lock will be removed
59 # even if recursive locking did occur
59 # even if recursive locking did occur
60 self.held = 1
60 self.held = 1
61
61
62 self.release()
62 self.release()
63
63
64 def lock(self):
64 def lock(self):
65 timeout = self.timeout
65 timeout = self.timeout
66 while True:
66 while True:
67 try:
67 try:
68 self._trylock()
68 self._trylock()
69 return self.timeout - timeout
69 return self.timeout - timeout
70 except error.LockHeld as inst:
70 except error.LockHeld as inst:
71 if timeout != 0:
71 if timeout != 0:
72 time.sleep(1)
72 time.sleep(1)
73 if timeout > 0:
73 if timeout > 0:
74 timeout -= 1
74 timeout -= 1
75 continue
75 continue
76 raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
76 raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
77 inst.locker)
77 inst.locker)
78
78
79 def _trylock(self):
79 def _trylock(self):
80 if self.held:
80 if self.held:
81 self.held += 1
81 self.held += 1
82 return
82 return
83 if lock._host is None:
83 if lock._host is None:
84 lock._host = socket.gethostname()
84 lock._host = socket.gethostname()
85 lockname = '%s:%s' % (lock._host, self.pid)
85 lockname = '%s:%s' % (lock._host, self.pid)
86 retry = 5
86 retry = 5
87 while not self.held and retry:
87 while not self.held and retry:
88 retry -= 1
88 retry -= 1
89 try:
89 try:
90 self.vfs.makelock(lockname, self.f)
90 self.vfs.makelock(lockname, self.f)
91 self.held = 1
91 self.held = 1
92 except (OSError, IOError) as why:
92 except (OSError, IOError) as why:
93 if why.errno == errno.EEXIST:
93 if why.errno == errno.EEXIST:
94 locker = self.testlock()
94 locker = self.testlock()
95 if locker is not None:
95 if locker is not None:
96 raise error.LockHeld(errno.EAGAIN,
96 raise error.LockHeld(errno.EAGAIN,
97 self.vfs.join(self.f), self.desc,
97 self.vfs.join(self.f), self.desc,
98 locker)
98 locker)
99 else:
99 else:
100 raise error.LockUnavailable(why.errno, why.strerror,
100 raise error.LockUnavailable(why.errno, why.strerror,
101 why.filename, self.desc)
101 why.filename, self.desc)
102
102
103 def _readlock(self):
103 def _readlock(self):
104 """read lock and return its value
104 """read lock and return its value
105
105
106 Returns None if no lock exists, pid for old-style locks, and host:pid
106 Returns None if no lock exists, pid for old-style locks, and host:pid
107 for new-style locks.
107 for new-style locks.
108 """
108 """
109 try:
109 try:
110 return self.vfs.readlock(self.f)
110 return self.vfs.readlock(self.f)
111 except (OSError, IOError) as why:
111 except (OSError, IOError) as why:
112 if why.errno == errno.ENOENT:
112 if why.errno == errno.ENOENT:
113 return None
113 return None
114 raise
114 raise
115
115
116 def testlock(self):
116 def _testlock(self, locker):
117 """return id of locker if lock is valid, else None.
118
119 If old-style lock, we cannot tell what machine locker is on.
120 with new-style lock, if locker is on this machine, we can
121 see if locker is alive. If locker is on this machine but
122 not alive, we can safely break lock.
123
124 The lock file is only deleted when None is returned.
125
126 """
127 locker = self._readlock()
128 if locker is None:
117 if locker is None:
129 return None
118 return None
130 try:
119 try:
131 host, pid = locker.split(":", 1)
120 host, pid = locker.split(":", 1)
132 except ValueError:
121 except ValueError:
133 return locker
122 return locker
134 if host != lock._host:
123 if host != lock._host:
135 return locker
124 return locker
136 try:
125 try:
137 pid = int(pid)
126 pid = int(pid)
138 except ValueError:
127 except ValueError:
139 return locker
128 return locker
140 if util.testpid(pid):
129 if util.testpid(pid):
141 return locker
130 return locker
142 # if locker dead, break lock. must do this with another lock
131 # if locker dead, break lock. must do this with another lock
143 # held, or can race and break valid lock.
132 # held, or can race and break valid lock.
144 try:
133 try:
145 l = lock(self.vfs, self.f + '.break', timeout=0)
134 l = lock(self.vfs, self.f + '.break', timeout=0)
146 self.vfs.unlink(self.f)
135 self.vfs.unlink(self.f)
147 l.release()
136 l.release()
148 except error.LockError:
137 except error.LockError:
149 return locker
138 return locker
150
139
140 def testlock(self):
141 """return id of locker if lock is valid, else None.
142
143 If old-style lock, we cannot tell what machine locker is on.
144 with new-style lock, if locker is on this machine, we can
145 see if locker is alive. If locker is on this machine but
146 not alive, we can safely break lock.
147
148 The lock file is only deleted when None is returned.
149
150 """
151 locker = self._readlock()
152 return self._testlock(locker)
153
151 def release(self):
154 def release(self):
152 """release the lock and execute callback function if any
155 """release the lock and execute callback function if any
153
156
154 If the lock has been acquired multiple times, the actual release is
157 If the lock has been acquired multiple times, the actual release is
155 delayed to the last release call."""
158 delayed to the last release call."""
156 if self.held > 1:
159 if self.held > 1:
157 self.held -= 1
160 self.held -= 1
158 elif self.held == 1:
161 elif self.held == 1:
159 self.held = 0
162 self.held = 0
160 if os.getpid() != self.pid:
163 if os.getpid() != self.pid:
161 # we forked, and are not the parent
164 # we forked, and are not the parent
162 return
165 return
163 try:
166 try:
164 if self.releasefn:
167 if self.releasefn:
165 self.releasefn()
168 self.releasefn()
166 finally:
169 finally:
167 try:
170 try:
168 self.vfs.unlink(self.f)
171 self.vfs.unlink(self.f)
169 except OSError:
172 except OSError:
170 pass
173 pass
171 for callback in self.postrelease:
174 for callback in self.postrelease:
172 callback()
175 callback()
173
176
174 def release(*locks):
177 def release(*locks):
175 for lock in locks:
178 for lock in locks:
176 if lock is not None:
179 if lock is not None:
177 lock.release()
180 lock.release()
General Comments 0
You need to be logged in to leave comments. Login now