##// END OF EJS Templates
lock: pass sysstr to warnings module...
Augie Fackler -
r37914:2efefde3 default
parent child Browse files
Show More
@@ -1,392 +1,392
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 signal
14 14 import socket
15 15 import time
16 16 import warnings
17 17
18 18 from .i18n import _
19 19
20 20 from . import (
21 21 encoding,
22 22 error,
23 23 pycompat,
24 24 )
25 25
26 26 from .utils import (
27 27 procutil,
28 28 )
29 29
30 30 def _getlockprefix():
31 31 """Return a string which is used to differentiate pid namespaces
32 32
33 33 It's useful to detect "dead" processes and remove stale locks with
34 34 confidence. Typically it's just hostname. On modern linux, we include an
35 35 extra Linux-specific pid namespace identifier.
36 36 """
37 37 result = encoding.strtolocal(socket.gethostname())
38 38 if pycompat.sysplatform.startswith('linux'):
39 39 try:
40 40 result += '/%x' % os.stat('/proc/self/ns/pid').st_ino
41 41 except OSError as ex:
42 42 if ex.errno not in (errno.ENOENT, errno.EACCES, errno.ENOTDIR):
43 43 raise
44 44 return result
45 45
46 46 @contextlib.contextmanager
47 47 def _delayedinterrupt():
48 48 """Block signal interrupt while doing something critical
49 49
50 50 This makes sure that the code block wrapped by this context manager won't
51 51 be interrupted.
52 52
53 53 For Windows developers: It appears not possible to guard time.sleep()
54 54 from CTRL_C_EVENT, so please don't use time.sleep() to test if this is
55 55 working.
56 56 """
57 57 assertedsigs = []
58 58 blocked = False
59 59 orighandlers = {}
60 60
61 61 def raiseinterrupt(num):
62 62 if (num == getattr(signal, 'SIGINT', None) or
63 63 num == getattr(signal, 'CTRL_C_EVENT', None)):
64 64 raise KeyboardInterrupt
65 65 else:
66 66 raise error.SignalInterrupt
67 67 def catchterm(num, frame):
68 68 if blocked:
69 69 assertedsigs.append(num)
70 70 else:
71 71 raiseinterrupt(num)
72 72
73 73 try:
74 74 # save handlers first so they can be restored even if a setup is
75 75 # interrupted between signal.signal() and orighandlers[] =.
76 76 for name in ['CTRL_C_EVENT', 'SIGINT', 'SIGBREAK', 'SIGHUP', 'SIGTERM']:
77 77 num = getattr(signal, name, None)
78 78 if num and num not in orighandlers:
79 79 orighandlers[num] = signal.getsignal(num)
80 80 try:
81 81 for num in orighandlers:
82 82 signal.signal(num, catchterm)
83 83 except ValueError:
84 84 pass # in a thread? no luck
85 85
86 86 blocked = True
87 87 yield
88 88 finally:
89 89 # no simple way to reliably restore all signal handlers because
90 90 # any loops, recursive function calls, except blocks, etc. can be
91 91 # interrupted. so instead, make catchterm() raise interrupt.
92 92 blocked = False
93 93 try:
94 94 for num, handler in orighandlers.items():
95 95 signal.signal(num, handler)
96 96 except ValueError:
97 97 pass # in a thread?
98 98
99 99 # re-raise interrupt exception if any, which may be shadowed by a new
100 100 # interrupt occurred while re-raising the first one
101 101 if assertedsigs:
102 102 raiseinterrupt(assertedsigs[0])
103 103
104 104 def trylock(ui, vfs, lockname, timeout, warntimeout, *args, **kwargs):
105 105 """return an acquired lock or raise an a LockHeld exception
106 106
107 107 This function is responsible to issue warnings and or debug messages about
108 108 the held lock while trying to acquires it."""
109 109
110 110 def printwarning(printer, locker):
111 111 """issue the usual "waiting on lock" message through any channel"""
112 112 # show more details for new-style locks
113 113 if ':' in locker:
114 114 host, pid = locker.split(":", 1)
115 115 msg = (_("waiting for lock on %s held by process %r on host %r\n")
116 116 % (pycompat.bytestr(l.desc), pycompat.bytestr(pid),
117 117 pycompat.bytestr(host)))
118 118 else:
119 119 msg = (_("waiting for lock on %s held by %r\n")
120 120 % (l.desc, pycompat.bytestr(locker)))
121 121 printer(msg)
122 122
123 123 l = lock(vfs, lockname, 0, *args, dolock=False, **kwargs)
124 124
125 125 debugidx = 0 if (warntimeout and timeout) else -1
126 126 warningidx = 0
127 127 if not timeout:
128 128 warningidx = -1
129 129 elif warntimeout:
130 130 warningidx = warntimeout
131 131
132 132 delay = 0
133 133 while True:
134 134 try:
135 135 l._trylock()
136 136 break
137 137 except error.LockHeld as inst:
138 138 if delay == debugidx:
139 139 printwarning(ui.debug, inst.locker)
140 140 if delay == warningidx:
141 141 printwarning(ui.warn, inst.locker)
142 142 if timeout <= delay:
143 143 raise error.LockHeld(errno.ETIMEDOUT, inst.filename,
144 144 l.desc, inst.locker)
145 145 time.sleep(1)
146 146 delay += 1
147 147
148 148 l.delay = delay
149 149 if l.delay:
150 150 if 0 <= warningidx <= l.delay:
151 151 ui.warn(_("got lock after %d seconds\n") % l.delay)
152 152 else:
153 153 ui.debug("got lock after %d seconds\n" % l.delay)
154 154 if l.acquirefn:
155 155 l.acquirefn()
156 156 return l
157 157
158 158 class lock(object):
159 159 '''An advisory lock held by one process to control access to a set
160 160 of files. Non-cooperating processes or incorrectly written scripts
161 161 can ignore Mercurial's locking scheme and stomp all over the
162 162 repository, so don't do that.
163 163
164 164 Typically used via localrepository.lock() to lock the repository
165 165 store (.hg/store/) or localrepository.wlock() to lock everything
166 166 else under .hg/.'''
167 167
168 168 # lock is symlink on platforms that support it, file on others.
169 169
170 170 # symlink is used because create of directory entry and contents
171 171 # are atomic even over nfs.
172 172
173 173 # old-style lock: symlink to pid
174 174 # new-style lock: symlink to hostname:pid
175 175
176 176 _host = None
177 177
178 178 def __init__(self, vfs, fname, timeout=-1, releasefn=None, acquirefn=None,
179 179 desc=None, inheritchecker=None, parentlock=None,
180 180 dolock=True):
181 181 self.vfs = vfs
182 182 self.f = fname
183 183 self.held = 0
184 184 self.timeout = timeout
185 185 self.releasefn = releasefn
186 186 self.acquirefn = acquirefn
187 187 self.desc = desc
188 188 self._inheritchecker = inheritchecker
189 189 self.parentlock = parentlock
190 190 self._parentheld = False
191 191 self._inherited = False
192 192 self.postrelease = []
193 193 self.pid = self._getpid()
194 194 if dolock:
195 195 self.delay = self.lock()
196 196 if self.acquirefn:
197 197 self.acquirefn()
198 198
199 199 def __enter__(self):
200 200 return self
201 201
202 202 def __exit__(self, exc_type, exc_value, exc_tb):
203 203 self.release()
204 204
205 205 def __del__(self):
206 206 if self.held:
207 warnings.warn("use lock.release instead of del lock",
207 warnings.warn(r"use lock.release instead of del lock",
208 208 category=DeprecationWarning,
209 209 stacklevel=2)
210 210
211 211 # ensure the lock will be removed
212 212 # even if recursive locking did occur
213 213 self.held = 1
214 214
215 215 self.release()
216 216
217 217 def _getpid(self):
218 218 # wrapper around procutil.getpid() to make testing easier
219 219 return procutil.getpid()
220 220
221 221 def lock(self):
222 222 timeout = self.timeout
223 223 while True:
224 224 try:
225 225 self._trylock()
226 226 return self.timeout - timeout
227 227 except error.LockHeld as inst:
228 228 if timeout != 0:
229 229 time.sleep(1)
230 230 if timeout > 0:
231 231 timeout -= 1
232 232 continue
233 233 raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
234 234 inst.locker)
235 235
236 236 def _trylock(self):
237 237 if self.held:
238 238 self.held += 1
239 239 return
240 240 if lock._host is None:
241 241 lock._host = _getlockprefix()
242 242 lockname = '%s:%d' % (lock._host, self.pid)
243 243 retry = 5
244 244 while not self.held and retry:
245 245 retry -= 1
246 246 try:
247 247 with _delayedinterrupt():
248 248 self.vfs.makelock(lockname, self.f)
249 249 self.held = 1
250 250 except (OSError, IOError) as why:
251 251 if why.errno == errno.EEXIST:
252 252 locker = self._readlock()
253 253 if locker is None:
254 254 continue
255 255
256 256 # special case where a parent process holds the lock -- this
257 257 # is different from the pid being different because we do
258 258 # want the unlock and postrelease functions to be called,
259 259 # but the lockfile to not be removed.
260 260 if locker == self.parentlock:
261 261 self._parentheld = True
262 262 self.held = 1
263 263 return
264 264 locker = self._testlock(locker)
265 265 if locker is not None:
266 266 raise error.LockHeld(errno.EAGAIN,
267 267 self.vfs.join(self.f), self.desc,
268 268 locker)
269 269 else:
270 270 raise error.LockUnavailable(why.errno, why.strerror,
271 271 why.filename, self.desc)
272 272
273 273 if not self.held:
274 274 # use empty locker to mean "busy for frequent lock/unlock
275 275 # by many processes"
276 276 raise error.LockHeld(errno.EAGAIN,
277 277 self.vfs.join(self.f), self.desc, "")
278 278
279 279 def _readlock(self):
280 280 """read lock and return its value
281 281
282 282 Returns None if no lock exists, pid for old-style locks, and host:pid
283 283 for new-style locks.
284 284 """
285 285 try:
286 286 return self.vfs.readlock(self.f)
287 287 except (OSError, IOError) as why:
288 288 if why.errno == errno.ENOENT:
289 289 return None
290 290 raise
291 291
292 292 def _testlock(self, locker):
293 293 if locker is None:
294 294 return None
295 295 try:
296 296 host, pid = locker.split(":", 1)
297 297 except ValueError:
298 298 return locker
299 299 if host != lock._host:
300 300 return locker
301 301 try:
302 302 pid = int(pid)
303 303 except ValueError:
304 304 return locker
305 305 if procutil.testpid(pid):
306 306 return locker
307 307 # if locker dead, break lock. must do this with another lock
308 308 # held, or can race and break valid lock.
309 309 try:
310 310 l = lock(self.vfs, self.f + '.break', timeout=0)
311 311 self.vfs.unlink(self.f)
312 312 l.release()
313 313 except error.LockError:
314 314 return locker
315 315
316 316 def testlock(self):
317 317 """return id of locker if lock is valid, else None.
318 318
319 319 If old-style lock, we cannot tell what machine locker is on.
320 320 with new-style lock, if locker is on this machine, we can
321 321 see if locker is alive. If locker is on this machine but
322 322 not alive, we can safely break lock.
323 323
324 324 The lock file is only deleted when None is returned.
325 325
326 326 """
327 327 locker = self._readlock()
328 328 return self._testlock(locker)
329 329
330 330 @contextlib.contextmanager
331 331 def inherit(self):
332 332 """context for the lock to be inherited by a Mercurial subprocess.
333 333
334 334 Yields a string that will be recognized by the lock in the subprocess.
335 335 Communicating this string to the subprocess needs to be done separately
336 336 -- typically by an environment variable.
337 337 """
338 338 if not self.held:
339 339 raise error.LockInheritanceContractViolation(
340 340 'inherit can only be called while lock is held')
341 341 if self._inherited:
342 342 raise error.LockInheritanceContractViolation(
343 343 'inherit cannot be called while lock is already inherited')
344 344 if self._inheritchecker is not None:
345 345 self._inheritchecker()
346 346 if self.releasefn:
347 347 self.releasefn()
348 348 if self._parentheld:
349 349 lockname = self.parentlock
350 350 else:
351 351 lockname = b'%s:%d' % (lock._host, self.pid)
352 352 self._inherited = True
353 353 try:
354 354 yield lockname
355 355 finally:
356 356 if self.acquirefn:
357 357 self.acquirefn()
358 358 self._inherited = False
359 359
360 360 def release(self):
361 361 """release the lock and execute callback function if any
362 362
363 363 If the lock has been acquired multiple times, the actual release is
364 364 delayed to the last release call."""
365 365 if self.held > 1:
366 366 self.held -= 1
367 367 elif self.held == 1:
368 368 self.held = 0
369 369 if self._getpid() != self.pid:
370 370 # we forked, and are not the parent
371 371 return
372 372 try:
373 373 if self.releasefn:
374 374 self.releasefn()
375 375 finally:
376 376 if not self._parentheld:
377 377 try:
378 378 self.vfs.unlink(self.f)
379 379 except OSError:
380 380 pass
381 381 # The postrelease functions typically assume the lock is not held
382 382 # at all.
383 383 if not self._parentheld:
384 384 for callback in self.postrelease:
385 385 callback()
386 386 # Prevent double usage and help clear cycles.
387 387 self.postrelease = None
388 388
389 389 def release(*locks):
390 390 for lock in locks:
391 391 if lock is not None:
392 392 lock.release()
General Comments 0
You need to be logged in to leave comments. Login now