##// END OF EJS Templates
cmdserver: add option to not exit from message loop on SIGINT...
Yuya Nishihara -
r45602:d6e99a44 default
parent child Browse files
Show More
@@ -244,8 +244,23 b' class server(object):'
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:
@@ -278,6 +293,32 b' class server(object):'
278 else:
293 else:
279 return []
294 return []
280
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
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 """
@@ -318,7 +359,10 b' class server(object):'
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
@@ -212,6 +212,9 b' coreconfigitem('
212 default=lambda: [b'chgserver', b'cmdserver', b'repocache'],
212 default=lambda: [b'chgserver', b'cmdserver', b'repocache'],
213 )
213 )
214 coreconfigitem(
214 coreconfigitem(
215 b'cmdserver', b'shutdown-on-interrupt', default=True,
216 )
217 coreconfigitem(
215 b'color', b'.*', default=None, generic=True,
218 b'color', b'.*', default=None, generic=True,
216 )
219 )
217 coreconfigitem(
220 coreconfigitem(
@@ -408,6 +408,18 b' Supported arguments:'
408 If no suitable authentication entry is found, the user is prompted
408 If no suitable authentication entry is found, the user is prompted
409 for credentials as usual if required by the remote.
409 for credentials as usual if required by the remote.
410
410
411 ``cmdserver``
412 -------------
413
414 Controls command server settings. (ADVANCED)
415
416 ``shutdown-on-interrupt``
417 If set to false, the server's main loop will continue running after
418 SIGINT received. ``runcommand`` requests can still be interrupted by
419 SIGINT. Close the write end of the pipe to shut down the server
420 process gracefully.
421 (default: True)
422
411 ``color``
423 ``color``
412 ---------
424 ---------
413
425
@@ -734,6 +734,51 b" don't fall back to cwd if invalid -R pat"
734 $ cd ..
734 $ cd ..
735
735
736
736
737 #if no-windows
738
739 option to not shutdown on SIGINT:
740
741 $ cat <<'EOF' > dbgint.py
742 > import os
743 > import signal
744 > import time
745 > from mercurial import commands, registrar
746 > cmdtable = {}
747 > command = registrar.command(cmdtable)
748 > @command(b"debugsleep", norepo=True)
749 > def debugsleep(ui):
750 > time.sleep(1)
751 > @command(b"debugsuicide", norepo=True)
752 > def debugsuicide(ui):
753 > os.kill(os.getpid(), signal.SIGINT)
754 > time.sleep(1)
755 > EOF
756
757 >>> import signal
758 >>> import time
759 >>> from hgclient import checkwith, readchannel, runcommand
760 >>> @checkwith(extraargs=[b'--config', b'cmdserver.shutdown-on-interrupt=False',
761 ... b'--config', b'extensions.dbgint=dbgint.py'])
762 ... def nointr(server):
763 ... readchannel(server)
764 ... server.send_signal(signal.SIGINT) # server won't be terminated
765 ... time.sleep(1)
766 ... runcommand(server, [b'debugsleep'])
767 ... server.send_signal(signal.SIGINT) # server won't be terminated
768 ... runcommand(server, [b'debugsleep'])
769 ... runcommand(server, [b'debugsuicide']) # command can be interrupted
770 ... server.send_signal(signal.SIGTERM) # server will be terminated
771 ... time.sleep(1)
772 *** runcommand debugsleep
773 *** runcommand debugsleep
774 *** runcommand debugsuicide
775 interrupted!
776 killed!
777 [255]
778
779 #endif
780
781
737 structured message channel:
782 structured message channel:
738
783
739 $ cat <<'EOF' >> repo2/.hg/hgrc
784 $ cat <<'EOF' >> repo2/.hg/hgrc
General Comments 0
You need to be logged in to leave comments. Login now