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