diff --git a/launch/launch/launch_service.py b/launch/launch/launch_service.py index 2e1719edf..a25672079 100644 --- a/launch/launch/launch_service.py +++ b/launch/launch/launch_service.py @@ -18,7 +18,6 @@ import collections.abc import contextlib import logging -import platform import signal import threading import traceback @@ -185,37 +184,28 @@ def _prepare_run_loop(self): this_task = asyncio.Task.current_task(this_loop) self.__this_task = this_task - # Setup custom signal handlers for SIGINT, SIGTERM and maybe SIGQUIT. - sigint_received = False - - def _on_sigint(signum): - nonlocal sigint_received - base_msg = 'user interrupted with ctrl-c (SIGINT)' - if not sigint_received: + # Setup custom signal handlers for SIGINT & SIGTERM + signals_received = set() + + def _on_signal(signum): + nonlocal signals_received + signal_name = signal.Signals(signum).name + base_msg = 'caught ' + signal_name + if signal_name not in signals_received: + signals_received.add(signal_name) + due_to_sigint = True if signal_name == 'SIGINT' else False self.__logger.warning(base_msg) ret = self._shutdown( - reason='ctrl-c (SIGINT)', due_to_sigint=True, force_sync=True + reason=base_msg, due_to_sigint=due_to_sigint, force_sync=True ) assert ret is None, ret - sigint_received = True else: self.__logger.warning('{} again, ignoring...'.format(base_msg)) - def _on_sigterm(signum): - signame = signal.Signals(signum).name - self.__logger.error( - 'user interrupted with ctrl-\\ ({}), terminating...'.format(signame)) - # TODO(wjwwood): try to terminate running subprocesses before exiting. - self.__logger.error('using {} can result in orphaned processes'.format(signame)) - self.__logger.error('make sure no processes launched are still running') - this_loop.call_soon(this_task.cancel) - with AsyncSafeSignalManager(this_loop) as manager: # Setup signal handlers - manager.handle(signal.SIGINT, _on_sigint) - manager.handle(signal.SIGTERM, _on_sigterm) - if platform.system() != 'Windows': - manager.handle(signal.SIGQUIT, _on_sigterm) + manager.handle(signal.SIGINT, _on_signal) + manager.handle(signal.SIGTERM, _on_signal) # Yield asyncio loop and current task. yield this_loop, this_task finally: diff --git a/launch/launch/utilities/signal_management.py b/launch/launch/utilities/signal_management.py index a078c69d7..ee9ab1365 100644 --- a/launch/launch/utilities/signal_management.py +++ b/launch/launch/utilities/signal_management.py @@ -235,6 +235,7 @@ def handle( :return: previous handler if any, otherwise None """ signum = signal.Signals(signum) + signal.signal(signum, signal.default_int_handler) if handler is not None: if not callable(handler): raise ValueError('signal handler must be a callable') diff --git a/launch/test/launch/utilities/test_signal_management.py b/launch/test/launch/utilities/test_signal_management.py index d95d2a9c7..15ceffcd4 100644 --- a/launch/test/launch/utilities/test_signal_management.py +++ b/launch/test/launch/utilities/test_signal_management.py @@ -25,19 +25,19 @@ def cap_signals(*signals): - def _noop(*args): - pass - def _decorator(func): @functools.wraps(func) def _wrapper(*args, **kwargs): handlers = {} try: for s in signals: - handlers[s] = signal.signal(s, _noop) + handlers[s] = signal.signal(s, signal.default_int_handler) return func(*args, **kwargs) + except KeyboardInterrupt: + pass finally: - assert all(signal.signal(s, h) is _noop for s, h in handlers.items()) + assert all(signal.signal(s, h) is signal.default_int_handler + for s, h in handlers.items()) return _wrapper return _decorator