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