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