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