##// END OF EJS Templates
lock: avoid unintentional lock acquisition at failure of readlock...
FUJIWARA Katsunori -
r32088:e1938d60 stable
parent child Browse files
Show More
@@ -1,264 +1,267 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 contextlib
10 import contextlib
11 import errno
11 import errno
12 import os
12 import os
13 import socket
13 import socket
14 import time
14 import time
15 import warnings
15 import warnings
16
16
17 from . import (
17 from . import (
18 encoding,
18 encoding,
19 error,
19 error,
20 pycompat,
20 pycompat,
21 util,
21 util,
22 )
22 )
23
23
24 def _getlockprefix():
24 def _getlockprefix():
25 """Return a string which is used to differentiate pid namespaces
25 """Return a string which is used to differentiate pid namespaces
26
26
27 It's useful to detect "dead" processes and remove stale locks with
27 It's useful to detect "dead" processes and remove stale locks with
28 confidence. Typically it's just hostname. On modern linux, we include an
28 confidence. Typically it's just hostname. On modern linux, we include an
29 extra Linux-specific pid namespace identifier.
29 extra Linux-specific pid namespace identifier.
30 """
30 """
31 result = socket.gethostname()
31 result = socket.gethostname()
32 if pycompat.ispy3:
32 if pycompat.ispy3:
33 result = result.encode(pycompat.sysstr(encoding.encoding), 'replace')
33 result = result.encode(pycompat.sysstr(encoding.encoding), 'replace')
34 if pycompat.sysplatform.startswith('linux'):
34 if pycompat.sysplatform.startswith('linux'):
35 try:
35 try:
36 result += '/%x' % os.stat('/proc/self/ns/pid').st_ino
36 result += '/%x' % os.stat('/proc/self/ns/pid').st_ino
37 except OSError as ex:
37 except OSError as ex:
38 if ex.errno not in (errno.ENOENT, errno.EACCES, errno.ENOTDIR):
38 if ex.errno not in (errno.ENOENT, errno.EACCES, errno.ENOTDIR):
39 raise
39 raise
40 return result
40 return result
41
41
42 class lock(object):
42 class lock(object):
43 '''An advisory lock held by one process to control access to a set
43 '''An advisory lock held by one process to control access to a set
44 of files. Non-cooperating processes or incorrectly written scripts
44 of files. Non-cooperating processes or incorrectly written scripts
45 can ignore Mercurial's locking scheme and stomp all over the
45 can ignore Mercurial's locking scheme and stomp all over the
46 repository, so don't do that.
46 repository, so don't do that.
47
47
48 Typically used via localrepository.lock() to lock the repository
48 Typically used via localrepository.lock() to lock the repository
49 store (.hg/store/) or localrepository.wlock() to lock everything
49 store (.hg/store/) or localrepository.wlock() to lock everything
50 else under .hg/.'''
50 else under .hg/.'''
51
51
52 # lock is symlink on platforms that support it, file on others.
52 # lock is symlink on platforms that support it, file on others.
53
53
54 # symlink is used because create of directory entry and contents
54 # symlink is used because create of directory entry and contents
55 # are atomic even over nfs.
55 # are atomic even over nfs.
56
56
57 # old-style lock: symlink to pid
57 # old-style lock: symlink to pid
58 # new-style lock: symlink to hostname:pid
58 # new-style lock: symlink to hostname:pid
59
59
60 _host = None
60 _host = None
61
61
62 def __init__(self, vfs, file, timeout=-1, releasefn=None, acquirefn=None,
62 def __init__(self, vfs, file, timeout=-1, releasefn=None, acquirefn=None,
63 desc=None, inheritchecker=None, parentlock=None):
63 desc=None, inheritchecker=None, parentlock=None):
64 self.vfs = vfs
64 self.vfs = vfs
65 self.f = file
65 self.f = file
66 self.held = 0
66 self.held = 0
67 self.timeout = timeout
67 self.timeout = timeout
68 self.releasefn = releasefn
68 self.releasefn = releasefn
69 self.acquirefn = acquirefn
69 self.acquirefn = acquirefn
70 self.desc = desc
70 self.desc = desc
71 self._inheritchecker = inheritchecker
71 self._inheritchecker = inheritchecker
72 self.parentlock = parentlock
72 self.parentlock = parentlock
73 self._parentheld = False
73 self._parentheld = False
74 self._inherited = False
74 self._inherited = False
75 self.postrelease = []
75 self.postrelease = []
76 self.pid = self._getpid()
76 self.pid = self._getpid()
77 self.delay = self.lock()
77 self.delay = self.lock()
78 if self.acquirefn:
78 if self.acquirefn:
79 self.acquirefn()
79 self.acquirefn()
80
80
81 def __enter__(self):
81 def __enter__(self):
82 return self
82 return self
83
83
84 def __exit__(self, exc_type, exc_value, exc_tb):
84 def __exit__(self, exc_type, exc_value, exc_tb):
85 self.release()
85 self.release()
86
86
87 def __del__(self):
87 def __del__(self):
88 if self.held:
88 if self.held:
89 warnings.warn("use lock.release instead of del lock",
89 warnings.warn("use lock.release instead of del lock",
90 category=DeprecationWarning,
90 category=DeprecationWarning,
91 stacklevel=2)
91 stacklevel=2)
92
92
93 # ensure the lock will be removed
93 # ensure the lock will be removed
94 # even if recursive locking did occur
94 # even if recursive locking did occur
95 self.held = 1
95 self.held = 1
96
96
97 self.release()
97 self.release()
98
98
99 def _getpid(self):
99 def _getpid(self):
100 # wrapper around util.getpid() to make testing easier
100 # wrapper around util.getpid() to make testing easier
101 return util.getpid()
101 return util.getpid()
102
102
103 def lock(self):
103 def lock(self):
104 timeout = self.timeout
104 timeout = self.timeout
105 while True:
105 while True:
106 try:
106 try:
107 self._trylock()
107 self._trylock()
108 return self.timeout - timeout
108 return self.timeout - timeout
109 except error.LockHeld as inst:
109 except error.LockHeld as inst:
110 if timeout != 0:
110 if timeout != 0:
111 time.sleep(1)
111 time.sleep(1)
112 if timeout > 0:
112 if timeout > 0:
113 timeout -= 1
113 timeout -= 1
114 continue
114 continue
115 raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
115 raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
116 inst.locker)
116 inst.locker)
117
117
118 def _trylock(self):
118 def _trylock(self):
119 if self.held:
119 if self.held:
120 self.held += 1
120 self.held += 1
121 return
121 return
122 if lock._host is None:
122 if lock._host is None:
123 lock._host = _getlockprefix()
123 lock._host = _getlockprefix()
124 lockname = '%s:%d' % (lock._host, self.pid)
124 lockname = '%s:%d' % (lock._host, self.pid)
125 retry = 5
125 retry = 5
126 while not self.held and retry:
126 while not self.held and retry:
127 retry -= 1
127 retry -= 1
128 try:
128 try:
129 self.vfs.makelock(lockname, self.f)
129 self.vfs.makelock(lockname, self.f)
130 self.held = 1
130 self.held = 1
131 except (OSError, IOError) as why:
131 except (OSError, IOError) as why:
132 if why.errno == errno.EEXIST:
132 if why.errno == errno.EEXIST:
133 locker = self._readlock()
133 locker = self._readlock()
134 if locker is None:
135 continue
136
134 # special case where a parent process holds the lock -- this
137 # special case where a parent process holds the lock -- this
135 # is different from the pid being different because we do
138 # is different from the pid being different because we do
136 # want the unlock and postrelease functions to be called,
139 # want the unlock and postrelease functions to be called,
137 # but the lockfile to not be removed.
140 # but the lockfile to not be removed.
138 if locker == self.parentlock:
141 if locker == self.parentlock:
139 self._parentheld = True
142 self._parentheld = True
140 self.held = 1
143 self.held = 1
141 return
144 return
142 locker = self._testlock(locker)
145 locker = self._testlock(locker)
143 if locker is not None:
146 if locker is not None:
144 raise error.LockHeld(errno.EAGAIN,
147 raise error.LockHeld(errno.EAGAIN,
145 self.vfs.join(self.f), self.desc,
148 self.vfs.join(self.f), self.desc,
146 locker)
149 locker)
147 else:
150 else:
148 raise error.LockUnavailable(why.errno, why.strerror,
151 raise error.LockUnavailable(why.errno, why.strerror,
149 why.filename, self.desc)
152 why.filename, self.desc)
150
153
151 def _readlock(self):
154 def _readlock(self):
152 """read lock and return its value
155 """read lock and return its value
153
156
154 Returns None if no lock exists, pid for old-style locks, and host:pid
157 Returns None if no lock exists, pid for old-style locks, and host:pid
155 for new-style locks.
158 for new-style locks.
156 """
159 """
157 try:
160 try:
158 return self.vfs.readlock(self.f)
161 return self.vfs.readlock(self.f)
159 except (OSError, IOError) as why:
162 except (OSError, IOError) as why:
160 if why.errno == errno.ENOENT:
163 if why.errno == errno.ENOENT:
161 return None
164 return None
162 raise
165 raise
163
166
164 def _testlock(self, locker):
167 def _testlock(self, locker):
165 if locker is None:
168 if locker is None:
166 return None
169 return None
167 try:
170 try:
168 host, pid = locker.split(":", 1)
171 host, pid = locker.split(":", 1)
169 except ValueError:
172 except ValueError:
170 return locker
173 return locker
171 if host != lock._host:
174 if host != lock._host:
172 return locker
175 return locker
173 try:
176 try:
174 pid = int(pid)
177 pid = int(pid)
175 except ValueError:
178 except ValueError:
176 return locker
179 return locker
177 if util.testpid(pid):
180 if util.testpid(pid):
178 return locker
181 return locker
179 # if locker dead, break lock. must do this with another lock
182 # if locker dead, break lock. must do this with another lock
180 # held, or can race and break valid lock.
183 # held, or can race and break valid lock.
181 try:
184 try:
182 l = lock(self.vfs, self.f + '.break', timeout=0)
185 l = lock(self.vfs, self.f + '.break', timeout=0)
183 self.vfs.unlink(self.f)
186 self.vfs.unlink(self.f)
184 l.release()
187 l.release()
185 except error.LockError:
188 except error.LockError:
186 return locker
189 return locker
187
190
188 def testlock(self):
191 def testlock(self):
189 """return id of locker if lock is valid, else None.
192 """return id of locker if lock is valid, else None.
190
193
191 If old-style lock, we cannot tell what machine locker is on.
194 If old-style lock, we cannot tell what machine locker is on.
192 with new-style lock, if locker is on this machine, we can
195 with new-style lock, if locker is on this machine, we can
193 see if locker is alive. If locker is on this machine but
196 see if locker is alive. If locker is on this machine but
194 not alive, we can safely break lock.
197 not alive, we can safely break lock.
195
198
196 The lock file is only deleted when None is returned.
199 The lock file is only deleted when None is returned.
197
200
198 """
201 """
199 locker = self._readlock()
202 locker = self._readlock()
200 return self._testlock(locker)
203 return self._testlock(locker)
201
204
202 @contextlib.contextmanager
205 @contextlib.contextmanager
203 def inherit(self):
206 def inherit(self):
204 """context for the lock to be inherited by a Mercurial subprocess.
207 """context for the lock to be inherited by a Mercurial subprocess.
205
208
206 Yields a string that will be recognized by the lock in the subprocess.
209 Yields a string that will be recognized by the lock in the subprocess.
207 Communicating this string to the subprocess needs to be done separately
210 Communicating this string to the subprocess needs to be done separately
208 -- typically by an environment variable.
211 -- typically by an environment variable.
209 """
212 """
210 if not self.held:
213 if not self.held:
211 raise error.LockInheritanceContractViolation(
214 raise error.LockInheritanceContractViolation(
212 'inherit can only be called while lock is held')
215 'inherit can only be called while lock is held')
213 if self._inherited:
216 if self._inherited:
214 raise error.LockInheritanceContractViolation(
217 raise error.LockInheritanceContractViolation(
215 'inherit cannot be called while lock is already inherited')
218 'inherit cannot be called while lock is already inherited')
216 if self._inheritchecker is not None:
219 if self._inheritchecker is not None:
217 self._inheritchecker()
220 self._inheritchecker()
218 if self.releasefn:
221 if self.releasefn:
219 self.releasefn()
222 self.releasefn()
220 if self._parentheld:
223 if self._parentheld:
221 lockname = self.parentlock
224 lockname = self.parentlock
222 else:
225 else:
223 lockname = '%s:%s' % (lock._host, self.pid)
226 lockname = '%s:%s' % (lock._host, self.pid)
224 self._inherited = True
227 self._inherited = True
225 try:
228 try:
226 yield lockname
229 yield lockname
227 finally:
230 finally:
228 if self.acquirefn:
231 if self.acquirefn:
229 self.acquirefn()
232 self.acquirefn()
230 self._inherited = False
233 self._inherited = False
231
234
232 def release(self):
235 def release(self):
233 """release the lock and execute callback function if any
236 """release the lock and execute callback function if any
234
237
235 If the lock has been acquired multiple times, the actual release is
238 If the lock has been acquired multiple times, the actual release is
236 delayed to the last release call."""
239 delayed to the last release call."""
237 if self.held > 1:
240 if self.held > 1:
238 self.held -= 1
241 self.held -= 1
239 elif self.held == 1:
242 elif self.held == 1:
240 self.held = 0
243 self.held = 0
241 if self._getpid() != self.pid:
244 if self._getpid() != self.pid:
242 # we forked, and are not the parent
245 # we forked, and are not the parent
243 return
246 return
244 try:
247 try:
245 if self.releasefn:
248 if self.releasefn:
246 self.releasefn()
249 self.releasefn()
247 finally:
250 finally:
248 if not self._parentheld:
251 if not self._parentheld:
249 try:
252 try:
250 self.vfs.unlink(self.f)
253 self.vfs.unlink(self.f)
251 except OSError:
254 except OSError:
252 pass
255 pass
253 # The postrelease functions typically assume the lock is not held
256 # The postrelease functions typically assume the lock is not held
254 # at all.
257 # at all.
255 if not self._parentheld:
258 if not self._parentheld:
256 for callback in self.postrelease:
259 for callback in self.postrelease:
257 callback()
260 callback()
258 # Prevent double usage and help clear cycles.
261 # Prevent double usage and help clear cycles.
259 self.postrelease = None
262 self.postrelease = None
260
263
261 def release(*locks):
264 def release(*locks):
262 for lock in locks:
265 for lock in locks:
263 if lock is not None:
266 if lock is not None:
264 lock.release()
267 lock.release()
General Comments 0
You need to be logged in to leave comments. Login now