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