Mercurial > public > mercurial-scm > hg-stable
diff 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 |
line wrap: on
line diff
--- a/mercurial/commandserver.py Mon Jul 06 17:51:18 2020 +0200 +++ b/mercurial/commandserver.py Sat Jun 27 21:46:23 2020 +0900 @@ -244,8 +244,23 @@ self.client = fin + # If shutdown-on-interrupt is off, the default SIGINT handler is + # removed so that client-server communication wouldn't be interrupted. + # For example, 'runcommand' handler will issue three short read()s. + # If one of the first two read()s were interrupted, the communication + # channel would be left at dirty state and the subsequent request + # wouldn't be parsed. So catching KeyboardInterrupt isn't enough. + self._shutdown_on_interrupt = ui.configbool( + b'cmdserver', b'shutdown-on-interrupt' + ) + self._old_inthandler = None + if not self._shutdown_on_interrupt: + self._old_inthandler = signal.signal(signal.SIGINT, signal.SIG_IGN) + def cleanup(self): """release and restore resources taken during server session""" + if not self._shutdown_on_interrupt: + signal.signal(signal.SIGINT, self._old_inthandler) def _read(self, size): if not size: @@ -278,6 +293,32 @@ else: return [] + def _dispatchcommand(self, req): + from . import dispatch # avoid cycle + + if self._shutdown_on_interrupt: + # no need to restore SIGINT handler as it is unmodified. + return dispatch.dispatch(req) + + try: + signal.signal(signal.SIGINT, self._old_inthandler) + return dispatch.dispatch(req) + except error.SignalInterrupt: + # propagate SIGBREAK, SIGHUP, or SIGTERM. + raise + except KeyboardInterrupt: + # SIGINT may be received out of the try-except block of dispatch(), + # so catch it as last ditch. Another KeyboardInterrupt may be + # raised while handling exceptions here, but there's no way to + # avoid that except for doing everything in C. + pass + finally: + signal.signal(signal.SIGINT, signal.SIG_IGN) + # On KeyboardInterrupt, print error message and exit *after* SIGINT + # handler removed. + req.ui.error(_(b'interrupted!\n')) + return -1 + def runcommand(self): """ reads a list of \0 terminated arguments, executes and writes the return code to the result channel """ @@ -318,7 +359,10 @@ ) try: - ret = dispatch.dispatch(req) & 255 + ret = self._dispatchcommand(req) & 255 + # If shutdown-on-interrupt is off, it's important to write the + # result code *after* SIGINT handler removed. If the result code + # were lost, the client wouldn't be able to continue processing. self.cresult.write(struct.pack(b'>i', int(ret))) finally: # restore old cwd