Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/commandserver.py @ 45058:d6e99a446eea
cmdserver: add option to not exit from message loop on SIGINT
Sending SIGINT to server is the only way to interrupt a command running in
command-server process. SIGINT will be caught at dispatch.dispatch() if
we're lucky. Otherwise it will terminate the serer process. This is
fundamentally unreliable as signals are delivered asynchronously.
"cmdserver.shutdown-on-interrupt=False" mitigate the issue by making the
server basically block SIGINT.
author | Yuya Nishihara <yuya@tcha.org> |
---|---|
date | Sat, 27 Jun 2020 21:46:23 +0900 |
parents | f5c006621f07 |
children | f43bc4ce0d69 |
comparison
equal
deleted
inserted
replaced
45057:9694895749ad | 45058:d6e99a446eea |
---|---|
242 encname, encfn = _selectmessageencoder(ui) | 242 encname, encfn = _selectmessageencoder(ui) |
243 self.cmsg = channeledmessage(fout, b'm', encname, encfn) | 243 self.cmsg = channeledmessage(fout, b'm', encname, encfn) |
244 | 244 |
245 self.client = fin | 245 self.client = fin |
246 | 246 |
247 # If shutdown-on-interrupt is off, the default SIGINT handler is | |
248 # removed so that client-server communication wouldn't be interrupted. | |
249 # For example, 'runcommand' handler will issue three short read()s. | |
250 # If one of the first two read()s were interrupted, the communication | |
251 # channel would be left at dirty state and the subsequent request | |
252 # wouldn't be parsed. So catching KeyboardInterrupt isn't enough. | |
253 self._shutdown_on_interrupt = ui.configbool( | |
254 b'cmdserver', b'shutdown-on-interrupt' | |
255 ) | |
256 self._old_inthandler = None | |
257 if not self._shutdown_on_interrupt: | |
258 self._old_inthandler = signal.signal(signal.SIGINT, signal.SIG_IGN) | |
259 | |
247 def cleanup(self): | 260 def cleanup(self): |
248 """release and restore resources taken during server session""" | 261 """release and restore resources taken during server session""" |
262 if not self._shutdown_on_interrupt: | |
263 signal.signal(signal.SIGINT, self._old_inthandler) | |
249 | 264 |
250 def _read(self, size): | 265 def _read(self, size): |
251 if not size: | 266 if not size: |
252 return b'' | 267 return b'' |
253 | 268 |
275 s = self._readstr() | 290 s = self._readstr() |
276 if s: | 291 if s: |
277 return s.split(b'\0') | 292 return s.split(b'\0') |
278 else: | 293 else: |
279 return [] | 294 return [] |
295 | |
296 def _dispatchcommand(self, req): | |
297 from . import dispatch # avoid cycle | |
298 | |
299 if self._shutdown_on_interrupt: | |
300 # no need to restore SIGINT handler as it is unmodified. | |
301 return dispatch.dispatch(req) | |
302 | |
303 try: | |
304 signal.signal(signal.SIGINT, self._old_inthandler) | |
305 return dispatch.dispatch(req) | |
306 except error.SignalInterrupt: | |
307 # propagate SIGBREAK, SIGHUP, or SIGTERM. | |
308 raise | |
309 except KeyboardInterrupt: | |
310 # SIGINT may be received out of the try-except block of dispatch(), | |
311 # so catch it as last ditch. Another KeyboardInterrupt may be | |
312 # raised while handling exceptions here, but there's no way to | |
313 # avoid that except for doing everything in C. | |
314 pass | |
315 finally: | |
316 signal.signal(signal.SIGINT, signal.SIG_IGN) | |
317 # On KeyboardInterrupt, print error message and exit *after* SIGINT | |
318 # handler removed. | |
319 req.ui.error(_(b'interrupted!\n')) | |
320 return -1 | |
280 | 321 |
281 def runcommand(self): | 322 def runcommand(self): |
282 """ reads a list of \0 terminated arguments, executes | 323 """ reads a list of \0 terminated arguments, executes |
283 and writes the return code to the result channel """ | 324 and writes the return code to the result channel """ |
284 from . import dispatch # avoid cycle | 325 from . import dispatch # avoid cycle |
316 self.cmsg, | 357 self.cmsg, |
317 prereposetups=self._prereposetups, | 358 prereposetups=self._prereposetups, |
318 ) | 359 ) |
319 | 360 |
320 try: | 361 try: |
321 ret = dispatch.dispatch(req) & 255 | 362 ret = self._dispatchcommand(req) & 255 |
363 # If shutdown-on-interrupt is off, it's important to write the | |
364 # result code *after* SIGINT handler removed. If the result code | |
365 # were lost, the client wouldn't be able to continue processing. | |
322 self.cresult.write(struct.pack(b'>i', int(ret))) | 366 self.cresult.write(struct.pack(b'>i', int(ret))) |
323 finally: | 367 finally: |
324 # restore old cwd | 368 # restore old cwd |
325 if b'--cwd' in args: | 369 if b'--cwd' in args: |
326 os.chdir(self.cwd) | 370 os.chdir(self.cwd) |