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