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