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