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