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