##// END OF EJS Templates
lock.release: do not unlink inherited locks...
Siddharth Agarwal -
r26359:c545d51c default
parent child Browse files
Show More
@@ -1,217 +1,218 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, acquirefn=None,
41 def __init__(self, vfs, file, timeout=-1, releasefn=None, acquirefn=None,
42 desc=None, parentlock=None):
42 desc=None, parentlock=None):
43 self.vfs = vfs
43 self.vfs = vfs
44 self.f = file
44 self.f = file
45 self.held = 0
45 self.held = 0
46 self.timeout = timeout
46 self.timeout = timeout
47 self.releasefn = releasefn
47 self.releasefn = releasefn
48 self.acquirefn = acquirefn
48 self.acquirefn = acquirefn
49 self.desc = desc
49 self.desc = desc
50 self.parentlock = parentlock
50 self.parentlock = parentlock
51 self._parentheld = False
51 self._parentheld = False
52 self._inherited = False
52 self._inherited = False
53 self.postrelease = []
53 self.postrelease = []
54 self.pid = os.getpid()
54 self.pid = os.getpid()
55 self.delay = self.lock()
55 self.delay = self.lock()
56 if self.acquirefn:
56 if self.acquirefn:
57 self.acquirefn()
57 self.acquirefn()
58
58
59 def __del__(self):
59 def __del__(self):
60 if self.held:
60 if self.held:
61 warnings.warn("use lock.release instead of del lock",
61 warnings.warn("use lock.release instead of del lock",
62 category=DeprecationWarning,
62 category=DeprecationWarning,
63 stacklevel=2)
63 stacklevel=2)
64
64
65 # ensure the lock will be removed
65 # ensure the lock will be removed
66 # even if recursive locking did occur
66 # even if recursive locking did occur
67 self.held = 1
67 self.held = 1
68
68
69 self.release()
69 self.release()
70
70
71 def lock(self):
71 def lock(self):
72 timeout = self.timeout
72 timeout = self.timeout
73 while True:
73 while True:
74 try:
74 try:
75 self._trylock()
75 self._trylock()
76 return self.timeout - timeout
76 return self.timeout - timeout
77 except error.LockHeld as inst:
77 except error.LockHeld as inst:
78 if timeout != 0:
78 if timeout != 0:
79 time.sleep(1)
79 time.sleep(1)
80 if timeout > 0:
80 if timeout > 0:
81 timeout -= 1
81 timeout -= 1
82 continue
82 continue
83 raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
83 raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
84 inst.locker)
84 inst.locker)
85
85
86 def _trylock(self):
86 def _trylock(self):
87 if self.held:
87 if self.held:
88 self.held += 1
88 self.held += 1
89 return
89 return
90 if lock._host is None:
90 if lock._host is None:
91 lock._host = socket.gethostname()
91 lock._host = socket.gethostname()
92 lockname = '%s:%s' % (lock._host, self.pid)
92 lockname = '%s:%s' % (lock._host, self.pid)
93 retry = 5
93 retry = 5
94 while not self.held and retry:
94 while not self.held and retry:
95 retry -= 1
95 retry -= 1
96 try:
96 try:
97 self.vfs.makelock(lockname, self.f)
97 self.vfs.makelock(lockname, self.f)
98 self.held = 1
98 self.held = 1
99 except (OSError, IOError) as why:
99 except (OSError, IOError) as why:
100 if why.errno == errno.EEXIST:
100 if why.errno == errno.EEXIST:
101 locker = self.testlock()
101 locker = self.testlock()
102 if locker is not None:
102 if locker is not None:
103 raise error.LockHeld(errno.EAGAIN,
103 raise error.LockHeld(errno.EAGAIN,
104 self.vfs.join(self.f), self.desc,
104 self.vfs.join(self.f), self.desc,
105 locker)
105 locker)
106 else:
106 else:
107 raise error.LockUnavailable(why.errno, why.strerror,
107 raise error.LockUnavailable(why.errno, why.strerror,
108 why.filename, self.desc)
108 why.filename, self.desc)
109
109
110 def _readlock(self):
110 def _readlock(self):
111 """read lock and return its value
111 """read lock and return its value
112
112
113 Returns None if no lock exists, pid for old-style locks, and host:pid
113 Returns None if no lock exists, pid for old-style locks, and host:pid
114 for new-style locks.
114 for new-style locks.
115 """
115 """
116 try:
116 try:
117 return self.vfs.readlock(self.f)
117 return self.vfs.readlock(self.f)
118 except (OSError, IOError) as why:
118 except (OSError, IOError) as why:
119 if why.errno == errno.ENOENT:
119 if why.errno == errno.ENOENT:
120 return None
120 return None
121 raise
121 raise
122
122
123 def _testlock(self, locker):
123 def _testlock(self, locker):
124 if locker is None:
124 if locker is None:
125 return None
125 return None
126 try:
126 try:
127 host, pid = locker.split(":", 1)
127 host, pid = locker.split(":", 1)
128 except ValueError:
128 except ValueError:
129 return locker
129 return locker
130 if host != lock._host:
130 if host != lock._host:
131 return locker
131 return locker
132 try:
132 try:
133 pid = int(pid)
133 pid = int(pid)
134 except ValueError:
134 except ValueError:
135 return locker
135 return locker
136 if util.testpid(pid):
136 if util.testpid(pid):
137 return locker
137 return locker
138 # if locker dead, break lock. must do this with another lock
138 # if locker dead, break lock. must do this with another lock
139 # held, or can race and break valid lock.
139 # held, or can race and break valid lock.
140 try:
140 try:
141 l = lock(self.vfs, self.f + '.break', timeout=0)
141 l = lock(self.vfs, self.f + '.break', timeout=0)
142 self.vfs.unlink(self.f)
142 self.vfs.unlink(self.f)
143 l.release()
143 l.release()
144 except error.LockError:
144 except error.LockError:
145 return locker
145 return locker
146
146
147 def testlock(self):
147 def testlock(self):
148 """return id of locker if lock is valid, else None.
148 """return id of locker if lock is valid, else None.
149
149
150 If old-style lock, we cannot tell what machine locker is on.
150 If old-style lock, we cannot tell what machine locker is on.
151 with new-style lock, if locker is on this machine, we can
151 with new-style lock, if locker is on this machine, we can
152 see if locker is alive. If locker is on this machine but
152 see if locker is alive. If locker is on this machine but
153 not alive, we can safely break lock.
153 not alive, we can safely break lock.
154
154
155 The lock file is only deleted when None is returned.
155 The lock file is only deleted when None is returned.
156
156
157 """
157 """
158 locker = self._readlock()
158 locker = self._readlock()
159 return self._testlock(locker)
159 return self._testlock(locker)
160
160
161 def prepinherit(self):
161 def prepinherit(self):
162 """prepare for the lock to be inherited by a Mercurial subprocess
162 """prepare for the lock to be inherited by a Mercurial subprocess
163
163
164 Returns a string that will be recognized by the lock in the
164 Returns a string that will be recognized by the lock in the
165 subprocess. Communicating this string to the subprocess needs to be done
165 subprocess. Communicating this string to the subprocess needs to be done
166 separately -- typically by an environment variable.
166 separately -- typically by an environment variable.
167 """
167 """
168 if not self.held:
168 if not self.held:
169 raise error.LockInheritanceContractViolation(
169 raise error.LockInheritanceContractViolation(
170 'prepinherit can only be called while lock is held')
170 'prepinherit can only be called while lock is held')
171 if self._inherited:
171 if self._inherited:
172 raise error.LockInheritanceContractViolation(
172 raise error.LockInheritanceContractViolation(
173 'prepinherit cannot be called while lock is already inherited')
173 'prepinherit cannot be called while lock is already inherited')
174 if self.releasefn:
174 if self.releasefn:
175 self.releasefn()
175 self.releasefn()
176 if self._parentheld:
176 if self._parentheld:
177 lockname = self.parentlock
177 lockname = self.parentlock
178 else:
178 else:
179 lockname = '%s:%s' % (lock._host, self.pid)
179 lockname = '%s:%s' % (lock._host, self.pid)
180 self._inherited = True
180 self._inherited = True
181 return lockname
181 return lockname
182
182
183 def reacquire(self):
183 def reacquire(self):
184 if not self._inherited:
184 if not self._inherited:
185 raise error.LockInheritanceContractViolation(
185 raise error.LockInheritanceContractViolation(
186 'reacquire can only be called after prepinherit')
186 'reacquire can only be called after prepinherit')
187 if self.acquirefn:
187 if self.acquirefn:
188 self.acquirefn()
188 self.acquirefn()
189 self._inherited = False
189 self._inherited = False
190
190
191 def release(self):
191 def release(self):
192 """release the lock and execute callback function if any
192 """release the lock and execute callback function if any
193
193
194 If the lock has been acquired multiple times, the actual release is
194 If the lock has been acquired multiple times, the actual release is
195 delayed to the last release call."""
195 delayed to the last release call."""
196 if self.held > 1:
196 if self.held > 1:
197 self.held -= 1
197 self.held -= 1
198 elif self.held == 1:
198 elif self.held == 1:
199 self.held = 0
199 self.held = 0
200 if os.getpid() != self.pid:
200 if os.getpid() != self.pid:
201 # we forked, and are not the parent
201 # we forked, and are not the parent
202 return
202 return
203 try:
203 try:
204 if self.releasefn:
204 if self.releasefn:
205 self.releasefn()
205 self.releasefn()
206 finally:
206 finally:
207 try:
207 if not self._parentheld:
208 self.vfs.unlink(self.f)
208 try:
209 except OSError:
209 self.vfs.unlink(self.f)
210 pass
210 except OSError:
211 pass
211 for callback in self.postrelease:
212 for callback in self.postrelease:
212 callback()
213 callback()
213
214
214 def release(*locks):
215 def release(*locks):
215 for lock in locks:
216 for lock in locks:
216 if lock is not None:
217 if lock is not None:
217 lock.release()
218 lock.release()
General Comments 0
You need to be logged in to leave comments. Login now