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