##// END OF EJS Templates
lock: add a method to prepare the lock for inheritance...
Siddharth Agarwal -
r26357:6979a136 default
parent child Browse files
Show More
@@ -1,187 +1,209 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 errno
10 import errno
11 import os
11 import os
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, parentlock=None):
42 desc=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.parentlock = parentlock
50 self.parentlock = parentlock
51 self._parentheld = False
51 self._parentheld = False
52 self._inherited = False
52 self._inherited = False
53 self.postrelease = []
53 self.postrelease = []
54 self.pid = os.getpid()
54 self.pid = os.getpid()
55 self.delay = self.lock()
55 self.delay = self.lock()
56 if self.acquirefn:
56 if self.acquirefn:
57 self.acquirefn()
57 self.acquirefn()
58
58
59 def __del__(self):
59 def __del__(self):
60 if self.held:
60 if self.held:
61 warnings.warn("use lock.release instead of del lock",
61 warnings.warn("use lock.release instead of del lock",
62 category=DeprecationWarning,
62 category=DeprecationWarning,
63 stacklevel=2)
63 stacklevel=2)
64
64
65 # ensure the lock will be removed
65 # ensure the lock will be removed
66 # even if recursive locking did occur
66 # even if recursive locking did occur
67 self.held = 1
67 self.held = 1
68
68
69 self.release()
69 self.release()
70
70
71 def lock(self):
71 def lock(self):
72 timeout = self.timeout
72 timeout = self.timeout
73 while True:
73 while True:
74 try:
74 try:
75 self._trylock()
75 self._trylock()
76 return self.timeout - timeout
76 return self.timeout - timeout
77 except error.LockHeld as inst:
77 except error.LockHeld as inst:
78 if timeout != 0:
78 if timeout != 0:
79 time.sleep(1)
79 time.sleep(1)
80 if timeout > 0:
80 if timeout > 0:
81 timeout -= 1
81 timeout -= 1
82 continue
82 continue
83 raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
83 raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
84 inst.locker)
84 inst.locker)
85
85
86 def _trylock(self):
86 def _trylock(self):
87 if self.held:
87 if self.held:
88 self.held += 1
88 self.held += 1
89 return
89 return
90 if lock._host is None:
90 if lock._host is None:
91 lock._host = socket.gethostname()
91 lock._host = socket.gethostname()
92 lockname = '%s:%s' % (lock._host, self.pid)
92 lockname = '%s:%s' % (lock._host, self.pid)
93 retry = 5
93 retry = 5
94 while not self.held and retry:
94 while not self.held and retry:
95 retry -= 1
95 retry -= 1
96 try:
96 try:
97 self.vfs.makelock(lockname, self.f)
97 self.vfs.makelock(lockname, self.f)
98 self.held = 1
98 self.held = 1
99 except (OSError, IOError) as why:
99 except (OSError, IOError) as why:
100 if why.errno == errno.EEXIST:
100 if why.errno == errno.EEXIST:
101 locker = self.testlock()
101 locker = self.testlock()
102 if locker is not None:
102 if locker is not None:
103 raise error.LockHeld(errno.EAGAIN,
103 raise error.LockHeld(errno.EAGAIN,
104 self.vfs.join(self.f), self.desc,
104 self.vfs.join(self.f), self.desc,
105 locker)
105 locker)
106 else:
106 else:
107 raise error.LockUnavailable(why.errno, why.strerror,
107 raise error.LockUnavailable(why.errno, why.strerror,
108 why.filename, self.desc)
108 why.filename, self.desc)
109
109
110 def _readlock(self):
110 def _readlock(self):
111 """read lock and return its value
111 """read lock and return its value
112
112
113 Returns None if no lock exists, pid for old-style locks, and host:pid
113 Returns None if no lock exists, pid for old-style locks, and host:pid
114 for new-style locks.
114 for new-style locks.
115 """
115 """
116 try:
116 try:
117 return self.vfs.readlock(self.f)
117 return self.vfs.readlock(self.f)
118 except (OSError, IOError) as why:
118 except (OSError, IOError) as why:
119 if why.errno == errno.ENOENT:
119 if why.errno == errno.ENOENT:
120 return None
120 return None
121 raise
121 raise
122
122
123 def _testlock(self, locker):
123 def _testlock(self, locker):
124 if locker is None:
124 if locker is None:
125 return None
125 return None
126 try:
126 try:
127 host, pid = locker.split(":", 1)
127 host, pid = locker.split(":", 1)
128 except ValueError:
128 except ValueError:
129 return locker
129 return locker
130 if host != lock._host:
130 if host != lock._host:
131 return locker
131 return locker
132 try:
132 try:
133 pid = int(pid)
133 pid = int(pid)
134 except ValueError:
134 except ValueError:
135 return locker
135 return locker
136 if util.testpid(pid):
136 if util.testpid(pid):
137 return locker
137 return locker
138 # if locker dead, break lock. must do this with another lock
138 # if locker dead, break lock. must do this with another lock
139 # held, or can race and break valid lock.
139 # held, or can race and break valid lock.
140 try:
140 try:
141 l = lock(self.vfs, self.f + '.break', timeout=0)
141 l = lock(self.vfs, self.f + '.break', timeout=0)
142 self.vfs.unlink(self.f)
142 self.vfs.unlink(self.f)
143 l.release()
143 l.release()
144 except error.LockError:
144 except error.LockError:
145 return locker
145 return locker
146
146
147 def testlock(self):
147 def testlock(self):
148 """return id of locker if lock is valid, else None.
148 """return id of locker if lock is valid, else None.
149
149
150 If old-style lock, we cannot tell what machine locker is on.
150 If old-style lock, we cannot tell what machine locker is on.
151 with new-style lock, if locker is on this machine, we can
151 with new-style lock, if locker is on this machine, we can
152 see if locker is alive. If locker is on this machine but
152 see if locker is alive. If locker is on this machine but
153 not alive, we can safely break lock.
153 not alive, we can safely break lock.
154
154
155 The lock file is only deleted when None is returned.
155 The lock file is only deleted when None is returned.
156
156
157 """
157 """
158 locker = self._readlock()
158 locker = self._readlock()
159 return self._testlock(locker)
159 return self._testlock(locker)
160
160
161 def prepinherit(self):
162 """prepare for the lock to be inherited by a Mercurial subprocess
163
164 Returns a string that will be recognized by the lock in the
165 subprocess. Communicating this string to the subprocess needs to be done
166 separately -- typically by an environment variable.
167 """
168 if not self.held:
169 raise error.LockInheritanceContractViolation(
170 'prepinherit can only be called while lock is held')
171 if self._inherited:
172 raise error.LockInheritanceContractViolation(
173 'prepinherit cannot be called while lock is already inherited')
174 if self.releasefn:
175 self.releasefn()
176 if self._parentheld:
177 lockname = self.parentlock
178 else:
179 lockname = '%s:%s' % (lock._host, self.pid)
180 self._inherited = True
181 return lockname
182
161 def release(self):
183 def release(self):
162 """release the lock and execute callback function if any
184 """release the lock and execute callback function if any
163
185
164 If the lock has been acquired multiple times, the actual release is
186 If the lock has been acquired multiple times, the actual release is
165 delayed to the last release call."""
187 delayed to the last release call."""
166 if self.held > 1:
188 if self.held > 1:
167 self.held -= 1
189 self.held -= 1
168 elif self.held == 1:
190 elif self.held == 1:
169 self.held = 0
191 self.held = 0
170 if os.getpid() != self.pid:
192 if os.getpid() != self.pid:
171 # we forked, and are not the parent
193 # we forked, and are not the parent
172 return
194 return
173 try:
195 try:
174 if self.releasefn:
196 if self.releasefn:
175 self.releasefn()
197 self.releasefn()
176 finally:
198 finally:
177 try:
199 try:
178 self.vfs.unlink(self.f)
200 self.vfs.unlink(self.f)
179 except OSError:
201 except OSError:
180 pass
202 pass
181 for callback in self.postrelease:
203 for callback in self.postrelease:
182 callback()
204 callback()
183
205
184 def release(*locks):
206 def release(*locks):
185 for lock in locks:
207 for lock in locks:
186 if lock is not None:
208 if lock is not None:
187 lock.release()
209 lock.release()
General Comments 0
You need to be logged in to leave comments. Login now