##// END OF EJS Templates
py3: fix formatting of lock error message
Yuya Nishihara -
r36660:f6efb3c0 default
parent child Browse files
Show More
@@ -1,329 +1,331
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 .i18n import _
17 from .i18n import _
18
18
19 from . import (
19 from . import (
20 encoding,
20 encoding,
21 error,
21 error,
22 pycompat,
22 pycompat,
23 util,
23 util,
24 )
24 )
25
25
26 def _getlockprefix():
26 def _getlockprefix():
27 """Return a string which is used to differentiate pid namespaces
27 """Return a string which is used to differentiate pid namespaces
28
28
29 It's useful to detect "dead" processes and remove stale locks with
29 It's useful to detect "dead" processes and remove stale locks with
30 confidence. Typically it's just hostname. On modern linux, we include an
30 confidence. Typically it's just hostname. On modern linux, we include an
31 extra Linux-specific pid namespace identifier.
31 extra Linux-specific pid namespace identifier.
32 """
32 """
33 result = encoding.strtolocal(socket.gethostname())
33 result = encoding.strtolocal(socket.gethostname())
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 def trylock(ui, vfs, lockname, timeout, warntimeout, *args, **kwargs):
42 def trylock(ui, vfs, lockname, timeout, warntimeout, *args, **kwargs):
43 """return an acquired lock or raise an a LockHeld exception
43 """return an acquired lock or raise an a LockHeld exception
44
44
45 This function is responsible to issue warnings and or debug messages about
45 This function is responsible to issue warnings and or debug messages about
46 the held lock while trying to acquires it."""
46 the held lock while trying to acquires it."""
47
47
48 def printwarning(printer, locker):
48 def printwarning(printer, locker):
49 """issue the usual "waiting on lock" message through any channel"""
49 """issue the usual "waiting on lock" message through any channel"""
50 # show more details for new-style locks
50 # show more details for new-style locks
51 if ':' in locker:
51 if ':' in locker:
52 host, pid = locker.split(":", 1)
52 host, pid = locker.split(":", 1)
53 msg = _("waiting for lock on %s held by process %r "
53 msg = (_("waiting for lock on %s held by process %r on host %r\n")
54 "on host %r\n") % (l.desc, pid, host)
54 % (pycompat.bytestr(l.desc), pycompat.bytestr(pid),
55 pycompat.bytestr(host)))
55 else:
56 else:
56 msg = _("waiting for lock on %s held by %r\n") % (l.desc, locker)
57 msg = (_("waiting for lock on %s held by %r\n")
58 % (l.desc, pycompat.bytestr(locker)))
57 printer(msg)
59 printer(msg)
58
60
59 l = lock(vfs, lockname, 0, *args, dolock=False, **kwargs)
61 l = lock(vfs, lockname, 0, *args, dolock=False, **kwargs)
60
62
61 debugidx = 0 if (warntimeout and timeout) else -1
63 debugidx = 0 if (warntimeout and timeout) else -1
62 warningidx = 0
64 warningidx = 0
63 if not timeout:
65 if not timeout:
64 warningidx = -1
66 warningidx = -1
65 elif warntimeout:
67 elif warntimeout:
66 warningidx = warntimeout
68 warningidx = warntimeout
67
69
68 delay = 0
70 delay = 0
69 while True:
71 while True:
70 try:
72 try:
71 l._trylock()
73 l._trylock()
72 break
74 break
73 except error.LockHeld as inst:
75 except error.LockHeld as inst:
74 if delay == debugidx:
76 if delay == debugidx:
75 printwarning(ui.debug, inst.locker)
77 printwarning(ui.debug, inst.locker)
76 if delay == warningidx:
78 if delay == warningidx:
77 printwarning(ui.warn, inst.locker)
79 printwarning(ui.warn, inst.locker)
78 if timeout <= delay:
80 if timeout <= delay:
79 raise error.LockHeld(errno.ETIMEDOUT, inst.filename,
81 raise error.LockHeld(errno.ETIMEDOUT, inst.filename,
80 l.desc, inst.locker)
82 l.desc, inst.locker)
81 time.sleep(1)
83 time.sleep(1)
82 delay += 1
84 delay += 1
83
85
84 l.delay = delay
86 l.delay = delay
85 if l.delay:
87 if l.delay:
86 if 0 <= warningidx <= l.delay:
88 if 0 <= warningidx <= l.delay:
87 ui.warn(_("got lock after %d seconds\n") % l.delay)
89 ui.warn(_("got lock after %d seconds\n") % l.delay)
88 else:
90 else:
89 ui.debug("got lock after %d seconds\n" % l.delay)
91 ui.debug("got lock after %d seconds\n" % l.delay)
90 if l.acquirefn:
92 if l.acquirefn:
91 l.acquirefn()
93 l.acquirefn()
92 return l
94 return l
93
95
94 class lock(object):
96 class lock(object):
95 '''An advisory lock held by one process to control access to a set
97 '''An advisory lock held by one process to control access to a set
96 of files. Non-cooperating processes or incorrectly written scripts
98 of files. Non-cooperating processes or incorrectly written scripts
97 can ignore Mercurial's locking scheme and stomp all over the
99 can ignore Mercurial's locking scheme and stomp all over the
98 repository, so don't do that.
100 repository, so don't do that.
99
101
100 Typically used via localrepository.lock() to lock the repository
102 Typically used via localrepository.lock() to lock the repository
101 store (.hg/store/) or localrepository.wlock() to lock everything
103 store (.hg/store/) or localrepository.wlock() to lock everything
102 else under .hg/.'''
104 else under .hg/.'''
103
105
104 # lock is symlink on platforms that support it, file on others.
106 # lock is symlink on platforms that support it, file on others.
105
107
106 # symlink is used because create of directory entry and contents
108 # symlink is used because create of directory entry and contents
107 # are atomic even over nfs.
109 # are atomic even over nfs.
108
110
109 # old-style lock: symlink to pid
111 # old-style lock: symlink to pid
110 # new-style lock: symlink to hostname:pid
112 # new-style lock: symlink to hostname:pid
111
113
112 _host = None
114 _host = None
113
115
114 def __init__(self, vfs, file, timeout=-1, releasefn=None, acquirefn=None,
116 def __init__(self, vfs, file, timeout=-1, releasefn=None, acquirefn=None,
115 desc=None, inheritchecker=None, parentlock=None,
117 desc=None, inheritchecker=None, parentlock=None,
116 dolock=True):
118 dolock=True):
117 self.vfs = vfs
119 self.vfs = vfs
118 self.f = file
120 self.f = file
119 self.held = 0
121 self.held = 0
120 self.timeout = timeout
122 self.timeout = timeout
121 self.releasefn = releasefn
123 self.releasefn = releasefn
122 self.acquirefn = acquirefn
124 self.acquirefn = acquirefn
123 self.desc = desc
125 self.desc = desc
124 self._inheritchecker = inheritchecker
126 self._inheritchecker = inheritchecker
125 self.parentlock = parentlock
127 self.parentlock = parentlock
126 self._parentheld = False
128 self._parentheld = False
127 self._inherited = False
129 self._inherited = False
128 self.postrelease = []
130 self.postrelease = []
129 self.pid = self._getpid()
131 self.pid = self._getpid()
130 if dolock:
132 if dolock:
131 self.delay = self.lock()
133 self.delay = self.lock()
132 if self.acquirefn:
134 if self.acquirefn:
133 self.acquirefn()
135 self.acquirefn()
134
136
135 def __enter__(self):
137 def __enter__(self):
136 return self
138 return self
137
139
138 def __exit__(self, exc_type, exc_value, exc_tb):
140 def __exit__(self, exc_type, exc_value, exc_tb):
139 self.release()
141 self.release()
140
142
141 def __del__(self):
143 def __del__(self):
142 if self.held:
144 if self.held:
143 warnings.warn("use lock.release instead of del lock",
145 warnings.warn("use lock.release instead of del lock",
144 category=DeprecationWarning,
146 category=DeprecationWarning,
145 stacklevel=2)
147 stacklevel=2)
146
148
147 # ensure the lock will be removed
149 # ensure the lock will be removed
148 # even if recursive locking did occur
150 # even if recursive locking did occur
149 self.held = 1
151 self.held = 1
150
152
151 self.release()
153 self.release()
152
154
153 def _getpid(self):
155 def _getpid(self):
154 # wrapper around util.getpid() to make testing easier
156 # wrapper around util.getpid() to make testing easier
155 return util.getpid()
157 return util.getpid()
156
158
157 def lock(self):
159 def lock(self):
158 timeout = self.timeout
160 timeout = self.timeout
159 while True:
161 while True:
160 try:
162 try:
161 self._trylock()
163 self._trylock()
162 return self.timeout - timeout
164 return self.timeout - timeout
163 except error.LockHeld as inst:
165 except error.LockHeld as inst:
164 if timeout != 0:
166 if timeout != 0:
165 time.sleep(1)
167 time.sleep(1)
166 if timeout > 0:
168 if timeout > 0:
167 timeout -= 1
169 timeout -= 1
168 continue
170 continue
169 raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
171 raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
170 inst.locker)
172 inst.locker)
171
173
172 def _trylock(self):
174 def _trylock(self):
173 if self.held:
175 if self.held:
174 self.held += 1
176 self.held += 1
175 return
177 return
176 if lock._host is None:
178 if lock._host is None:
177 lock._host = _getlockprefix()
179 lock._host = _getlockprefix()
178 lockname = '%s:%d' % (lock._host, self.pid)
180 lockname = '%s:%d' % (lock._host, self.pid)
179 retry = 5
181 retry = 5
180 while not self.held and retry:
182 while not self.held and retry:
181 retry -= 1
183 retry -= 1
182 try:
184 try:
183 self.vfs.makelock(lockname, self.f)
185 self.vfs.makelock(lockname, self.f)
184 self.held = 1
186 self.held = 1
185 except (OSError, IOError) as why:
187 except (OSError, IOError) as why:
186 if why.errno == errno.EEXIST:
188 if why.errno == errno.EEXIST:
187 locker = self._readlock()
189 locker = self._readlock()
188 if locker is None:
190 if locker is None:
189 continue
191 continue
190
192
191 # special case where a parent process holds the lock -- this
193 # special case where a parent process holds the lock -- this
192 # is different from the pid being different because we do
194 # is different from the pid being different because we do
193 # want the unlock and postrelease functions to be called,
195 # want the unlock and postrelease functions to be called,
194 # but the lockfile to not be removed.
196 # but the lockfile to not be removed.
195 if locker == self.parentlock:
197 if locker == self.parentlock:
196 self._parentheld = True
198 self._parentheld = True
197 self.held = 1
199 self.held = 1
198 return
200 return
199 locker = self._testlock(locker)
201 locker = self._testlock(locker)
200 if locker is not None:
202 if locker is not None:
201 raise error.LockHeld(
203 raise error.LockHeld(
202 errno.EAGAIN,
204 errno.EAGAIN,
203 encoding.strfromlocal(self.vfs.join(self.f)),
205 encoding.strfromlocal(self.vfs.join(self.f)),
204 self.desc, locker)
206 self.desc, locker)
205 else:
207 else:
206 raise error.LockUnavailable(why.errno, why.strerror,
208 raise error.LockUnavailable(why.errno, why.strerror,
207 why.filename, self.desc)
209 why.filename, self.desc)
208
210
209 if not self.held:
211 if not self.held:
210 # use empty locker to mean "busy for frequent lock/unlock
212 # use empty locker to mean "busy for frequent lock/unlock
211 # by many processes"
213 # by many processes"
212 raise error.LockHeld(errno.EAGAIN,
214 raise error.LockHeld(errno.EAGAIN,
213 encoding.strfromlocal(self.vfs.join(self.f)),
215 encoding.strfromlocal(self.vfs.join(self.f)),
214 self.desc, "")
216 self.desc, "")
215
217
216 def _readlock(self):
218 def _readlock(self):
217 """read lock and return its value
219 """read lock and return its value
218
220
219 Returns None if no lock exists, pid for old-style locks, and host:pid
221 Returns None if no lock exists, pid for old-style locks, and host:pid
220 for new-style locks.
222 for new-style locks.
221 """
223 """
222 try:
224 try:
223 return self.vfs.readlock(self.f)
225 return self.vfs.readlock(self.f)
224 except (OSError, IOError) as why:
226 except (OSError, IOError) as why:
225 if why.errno == errno.ENOENT:
227 if why.errno == errno.ENOENT:
226 return None
228 return None
227 raise
229 raise
228
230
229 def _testlock(self, locker):
231 def _testlock(self, locker):
230 if locker is None:
232 if locker is None:
231 return None
233 return None
232 try:
234 try:
233 host, pid = locker.split(":", 1)
235 host, pid = locker.split(":", 1)
234 except ValueError:
236 except ValueError:
235 return locker
237 return locker
236 if host != lock._host:
238 if host != lock._host:
237 return locker
239 return locker
238 try:
240 try:
239 pid = int(pid)
241 pid = int(pid)
240 except ValueError:
242 except ValueError:
241 return locker
243 return locker
242 if util.testpid(pid):
244 if util.testpid(pid):
243 return locker
245 return locker
244 # if locker dead, break lock. must do this with another lock
246 # if locker dead, break lock. must do this with another lock
245 # held, or can race and break valid lock.
247 # held, or can race and break valid lock.
246 try:
248 try:
247 l = lock(self.vfs, self.f + '.break', timeout=0)
249 l = lock(self.vfs, self.f + '.break', timeout=0)
248 self.vfs.unlink(self.f)
250 self.vfs.unlink(self.f)
249 l.release()
251 l.release()
250 except error.LockError:
252 except error.LockError:
251 return locker
253 return locker
252
254
253 def testlock(self):
255 def testlock(self):
254 """return id of locker if lock is valid, else None.
256 """return id of locker if lock is valid, else None.
255
257
256 If old-style lock, we cannot tell what machine locker is on.
258 If old-style lock, we cannot tell what machine locker is on.
257 with new-style lock, if locker is on this machine, we can
259 with new-style lock, if locker is on this machine, we can
258 see if locker is alive. If locker is on this machine but
260 see if locker is alive. If locker is on this machine but
259 not alive, we can safely break lock.
261 not alive, we can safely break lock.
260
262
261 The lock file is only deleted when None is returned.
263 The lock file is only deleted when None is returned.
262
264
263 """
265 """
264 locker = self._readlock()
266 locker = self._readlock()
265 return self._testlock(locker)
267 return self._testlock(locker)
266
268
267 @contextlib.contextmanager
269 @contextlib.contextmanager
268 def inherit(self):
270 def inherit(self):
269 """context for the lock to be inherited by a Mercurial subprocess.
271 """context for the lock to be inherited by a Mercurial subprocess.
270
272
271 Yields a string that will be recognized by the lock in the subprocess.
273 Yields a string that will be recognized by the lock in the subprocess.
272 Communicating this string to the subprocess needs to be done separately
274 Communicating this string to the subprocess needs to be done separately
273 -- typically by an environment variable.
275 -- typically by an environment variable.
274 """
276 """
275 if not self.held:
277 if not self.held:
276 raise error.LockInheritanceContractViolation(
278 raise error.LockInheritanceContractViolation(
277 'inherit can only be called while lock is held')
279 'inherit can only be called while lock is held')
278 if self._inherited:
280 if self._inherited:
279 raise error.LockInheritanceContractViolation(
281 raise error.LockInheritanceContractViolation(
280 'inherit cannot be called while lock is already inherited')
282 'inherit cannot be called while lock is already inherited')
281 if self._inheritchecker is not None:
283 if self._inheritchecker is not None:
282 self._inheritchecker()
284 self._inheritchecker()
283 if self.releasefn:
285 if self.releasefn:
284 self.releasefn()
286 self.releasefn()
285 if self._parentheld:
287 if self._parentheld:
286 lockname = self.parentlock
288 lockname = self.parentlock
287 else:
289 else:
288 lockname = '%s:%s' % (lock._host, self.pid)
290 lockname = '%s:%s' % (lock._host, self.pid)
289 self._inherited = True
291 self._inherited = True
290 try:
292 try:
291 yield lockname
293 yield lockname
292 finally:
294 finally:
293 if self.acquirefn:
295 if self.acquirefn:
294 self.acquirefn()
296 self.acquirefn()
295 self._inherited = False
297 self._inherited = False
296
298
297 def release(self):
299 def release(self):
298 """release the lock and execute callback function if any
300 """release the lock and execute callback function if any
299
301
300 If the lock has been acquired multiple times, the actual release is
302 If the lock has been acquired multiple times, the actual release is
301 delayed to the last release call."""
303 delayed to the last release call."""
302 if self.held > 1:
304 if self.held > 1:
303 self.held -= 1
305 self.held -= 1
304 elif self.held == 1:
306 elif self.held == 1:
305 self.held = 0
307 self.held = 0
306 if self._getpid() != self.pid:
308 if self._getpid() != self.pid:
307 # we forked, and are not the parent
309 # we forked, and are not the parent
308 return
310 return
309 try:
311 try:
310 if self.releasefn:
312 if self.releasefn:
311 self.releasefn()
313 self.releasefn()
312 finally:
314 finally:
313 if not self._parentheld:
315 if not self._parentheld:
314 try:
316 try:
315 self.vfs.unlink(self.f)
317 self.vfs.unlink(self.f)
316 except OSError:
318 except OSError:
317 pass
319 pass
318 # The postrelease functions typically assume the lock is not held
320 # The postrelease functions typically assume the lock is not held
319 # at all.
321 # at all.
320 if not self._parentheld:
322 if not self._parentheld:
321 for callback in self.postrelease:
323 for callback in self.postrelease:
322 callback()
324 callback()
323 # Prevent double usage and help clear cycles.
325 # Prevent double usage and help clear cycles.
324 self.postrelease = None
326 self.postrelease = None
325
327
326 def release(*locks):
328 def release(*locks):
327 for lock in locks:
329 for lock in locks:
328 if lock is not None:
330 if lock is not None:
329 lock.release()
331 lock.release()
General Comments 0
You need to be logged in to leave comments. Login now