41 result += '/%x' % os.stat('/proc/self/ns/pid').st_ino |
40 result += '/%x' % os.stat('/proc/self/ns/pid').st_ino |
42 except OSError as ex: |
41 except OSError as ex: |
43 if ex.errno not in (errno.ENOENT, errno.EACCES, errno.ENOTDIR): |
42 if ex.errno not in (errno.ENOENT, errno.EACCES, errno.ENOTDIR): |
44 raise |
43 raise |
45 return result |
44 return result |
|
45 |
46 |
46 |
47 @contextlib.contextmanager |
47 @contextlib.contextmanager |
48 def _delayedinterrupt(): |
48 def _delayedinterrupt(): |
49 """Block signal interrupt while doing something critical |
49 """Block signal interrupt while doing something critical |
50 |
50 |
58 assertedsigs = [] |
58 assertedsigs = [] |
59 blocked = False |
59 blocked = False |
60 orighandlers = {} |
60 orighandlers = {} |
61 |
61 |
62 def raiseinterrupt(num): |
62 def raiseinterrupt(num): |
63 if (num == getattr(signal, 'SIGINT', None) or |
63 if num == getattr(signal, 'SIGINT', None) or num == getattr( |
64 num == getattr(signal, 'CTRL_C_EVENT', None)): |
64 signal, 'CTRL_C_EVENT', None |
|
65 ): |
65 raise KeyboardInterrupt |
66 raise KeyboardInterrupt |
66 else: |
67 else: |
67 raise error.SignalInterrupt |
68 raise error.SignalInterrupt |
|
69 |
68 def catchterm(num, frame): |
70 def catchterm(num, frame): |
69 if blocked: |
71 if blocked: |
70 assertedsigs.append(num) |
72 assertedsigs.append(num) |
71 else: |
73 else: |
72 raiseinterrupt(num) |
74 raiseinterrupt(num) |
80 orighandlers[num] = signal.getsignal(num) |
82 orighandlers[num] = signal.getsignal(num) |
81 try: |
83 try: |
82 for num in orighandlers: |
84 for num in orighandlers: |
83 signal.signal(num, catchterm) |
85 signal.signal(num, catchterm) |
84 except ValueError: |
86 except ValueError: |
85 pass # in a thread? no luck |
87 pass # in a thread? no luck |
86 |
88 |
87 blocked = True |
89 blocked = True |
88 yield |
90 yield |
89 finally: |
91 finally: |
90 # no simple way to reliably restore all signal handlers because |
92 # no simple way to reliably restore all signal handlers because |
93 blocked = False |
95 blocked = False |
94 try: |
96 try: |
95 for num, handler in orighandlers.items(): |
97 for num, handler in orighandlers.items(): |
96 signal.signal(num, handler) |
98 signal.signal(num, handler) |
97 except ValueError: |
99 except ValueError: |
98 pass # in a thread? |
100 pass # in a thread? |
99 |
101 |
100 # re-raise interrupt exception if any, which may be shadowed by a new |
102 # re-raise interrupt exception if any, which may be shadowed by a new |
101 # interrupt occurred while re-raising the first one |
103 # interrupt occurred while re-raising the first one |
102 if assertedsigs: |
104 if assertedsigs: |
103 raiseinterrupt(assertedsigs[0]) |
105 raiseinterrupt(assertedsigs[0]) |
|
106 |
104 |
107 |
105 def trylock(ui, vfs, lockname, timeout, warntimeout, *args, **kwargs): |
108 def trylock(ui, vfs, lockname, timeout, warntimeout, *args, **kwargs): |
106 """return an acquired lock or raise an a LockHeld exception |
109 """return an acquired lock or raise an a LockHeld exception |
107 |
110 |
108 This function is responsible to issue warnings and or debug messages about |
111 This function is responsible to issue warnings and or debug messages about |
111 def printwarning(printer, locker): |
114 def printwarning(printer, locker): |
112 """issue the usual "waiting on lock" message through any channel""" |
115 """issue the usual "waiting on lock" message through any channel""" |
113 # show more details for new-style locks |
116 # show more details for new-style locks |
114 if ':' in locker: |
117 if ':' in locker: |
115 host, pid = locker.split(":", 1) |
118 host, pid = locker.split(":", 1) |
116 msg = (_("waiting for lock on %s held by process %r on host %r\n") |
119 msg = _( |
117 % (pycompat.bytestr(l.desc), pycompat.bytestr(pid), |
120 "waiting for lock on %s held by process %r on host %r\n" |
118 pycompat.bytestr(host))) |
121 ) % ( |
119 else: |
122 pycompat.bytestr(l.desc), |
120 msg = (_("waiting for lock on %s held by %r\n") |
123 pycompat.bytestr(pid), |
121 % (l.desc, pycompat.bytestr(locker))) |
124 pycompat.bytestr(host), |
|
125 ) |
|
126 else: |
|
127 msg = _("waiting for lock on %s held by %r\n") % ( |
|
128 l.desc, |
|
129 pycompat.bytestr(locker), |
|
130 ) |
122 printer(msg) |
131 printer(msg) |
123 |
132 |
124 l = lock(vfs, lockname, 0, *args, dolock=False, **kwargs) |
133 l = lock(vfs, lockname, 0, *args, dolock=False, **kwargs) |
125 |
134 |
126 debugidx = 0 if (warntimeout and timeout) else -1 |
135 debugidx = 0 if (warntimeout and timeout) else -1 |
139 if delay == debugidx: |
148 if delay == debugidx: |
140 printwarning(ui.debug, inst.locker) |
149 printwarning(ui.debug, inst.locker) |
141 if delay == warningidx: |
150 if delay == warningidx: |
142 printwarning(ui.warn, inst.locker) |
151 printwarning(ui.warn, inst.locker) |
143 if timeout <= delay: |
152 if timeout <= delay: |
144 raise error.LockHeld(errno.ETIMEDOUT, inst.filename, |
153 raise error.LockHeld( |
145 l.desc, inst.locker) |
154 errno.ETIMEDOUT, inst.filename, l.desc, inst.locker |
|
155 ) |
146 time.sleep(1) |
156 time.sleep(1) |
147 delay += 1 |
157 delay += 1 |
148 |
158 |
149 l.delay = delay |
159 l.delay = delay |
150 if l.delay: |
160 if l.delay: |
174 # old-style lock: symlink to pid |
185 # old-style lock: symlink to pid |
175 # new-style lock: symlink to hostname:pid |
186 # new-style lock: symlink to hostname:pid |
176 |
187 |
177 _host = None |
188 _host = None |
178 |
189 |
179 def __init__(self, vfs, fname, timeout=-1, releasefn=None, acquirefn=None, |
190 def __init__( |
180 desc=None, inheritchecker=None, parentlock=None, |
191 self, |
181 signalsafe=True, dolock=True): |
192 vfs, |
|
193 fname, |
|
194 timeout=-1, |
|
195 releasefn=None, |
|
196 acquirefn=None, |
|
197 desc=None, |
|
198 inheritchecker=None, |
|
199 parentlock=None, |
|
200 signalsafe=True, |
|
201 dolock=True, |
|
202 ): |
182 self.vfs = vfs |
203 self.vfs = vfs |
183 self.f = fname |
204 self.f = fname |
184 self.held = 0 |
205 self.held = 0 |
185 self.timeout = timeout |
206 self.timeout = timeout |
186 self.releasefn = releasefn |
207 self.releasefn = releasefn |
192 self._inherited = False |
213 self._inherited = False |
193 if signalsafe: |
214 if signalsafe: |
194 self._maybedelayedinterrupt = _delayedinterrupt |
215 self._maybedelayedinterrupt = _delayedinterrupt |
195 else: |
216 else: |
196 self._maybedelayedinterrupt = util.nullcontextmanager |
217 self._maybedelayedinterrupt = util.nullcontextmanager |
197 self.postrelease = [] |
218 self.postrelease = [] |
198 self.pid = self._getpid() |
219 self.pid = self._getpid() |
199 if dolock: |
220 if dolock: |
200 self.delay = self.lock() |
221 self.delay = self.lock() |
201 if self.acquirefn: |
222 if self.acquirefn: |
202 self.acquirefn() |
223 self.acquirefn() |
207 def __exit__(self, exc_type, exc_value, exc_tb): |
228 def __exit__(self, exc_type, exc_value, exc_tb): |
208 self.release() |
229 self.release() |
209 |
230 |
210 def __del__(self): |
231 def __del__(self): |
211 if self.held: |
232 if self.held: |
212 warnings.warn(r"use lock.release instead of del lock", |
233 warnings.warn( |
213 category=DeprecationWarning, |
234 r"use lock.release instead of del lock", |
214 stacklevel=2) |
235 category=DeprecationWarning, |
|
236 stacklevel=2, |
|
237 ) |
215 |
238 |
216 # ensure the lock will be removed |
239 # ensure the lock will be removed |
217 # even if recursive locking did occur |
240 # even if recursive locking did occur |
218 self.held = 1 |
241 self.held = 1 |
219 |
242 |
266 self._parentheld = True |
290 self._parentheld = True |
267 self.held = 1 |
291 self.held = 1 |
268 return |
292 return |
269 locker = self._testlock(locker) |
293 locker = self._testlock(locker) |
270 if locker is not None: |
294 if locker is not None: |
271 raise error.LockHeld(errno.EAGAIN, |
295 raise error.LockHeld( |
272 self.vfs.join(self.f), self.desc, |
296 errno.EAGAIN, |
273 locker) |
297 self.vfs.join(self.f), |
|
298 self.desc, |
|
299 locker, |
|
300 ) |
274 else: |
301 else: |
275 raise error.LockUnavailable(why.errno, why.strerror, |
302 raise error.LockUnavailable( |
276 why.filename, self.desc) |
303 why.errno, why.strerror, why.filename, self.desc |
|
304 ) |
277 |
305 |
278 if not self.held: |
306 if not self.held: |
279 # use empty locker to mean "busy for frequent lock/unlock |
307 # use empty locker to mean "busy for frequent lock/unlock |
280 # by many processes" |
308 # by many processes" |
281 raise error.LockHeld(errno.EAGAIN, |
309 raise error.LockHeld( |
282 self.vfs.join(self.f), self.desc, "") |
310 errno.EAGAIN, self.vfs.join(self.f), self.desc, "" |
|
311 ) |
283 |
312 |
284 def _readlock(self): |
313 def _readlock(self): |
285 """read lock and return its value |
314 """read lock and return its value |
286 |
315 |
287 Returns None if no lock exists, pid for old-style locks, and host:pid |
316 Returns None if no lock exists, pid for old-style locks, and host:pid |
340 Communicating this string to the subprocess needs to be done separately |
369 Communicating this string to the subprocess needs to be done separately |
341 -- typically by an environment variable. |
370 -- typically by an environment variable. |
342 """ |
371 """ |
343 if not self.held: |
372 if not self.held: |
344 raise error.LockInheritanceContractViolation( |
373 raise error.LockInheritanceContractViolation( |
345 'inherit can only be called while lock is held') |
374 'inherit can only be called while lock is held' |
|
375 ) |
346 if self._inherited: |
376 if self._inherited: |
347 raise error.LockInheritanceContractViolation( |
377 raise error.LockInheritanceContractViolation( |
348 'inherit cannot be called while lock is already inherited') |
378 'inherit cannot be called while lock is already inherited' |
|
379 ) |
349 if self._inheritchecker is not None: |
380 if self._inheritchecker is not None: |
350 self._inheritchecker() |
381 self._inheritchecker() |
351 if self.releasefn: |
382 if self.releasefn: |
352 self.releasefn() |
383 self.releasefn() |
353 if self._parentheld: |
384 if self._parentheld: |