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