Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore original signal handlers in finished_callback #1402

Open
wants to merge 1 commit into
base: devel
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions src/ansible_runner/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#
import os
import json
import signal
import sys
import threading
import logging
Expand Down Expand Up @@ -89,11 +90,26 @@ def init_runner(**kwargs):
status_callback_handler = kwargs.pop('status_handler', None)
artifacts_handler = kwargs.pop('artifacts_handler', None)
cancel_callback = kwargs.pop('cancel_callback', None)
finished_callback = kwargs.pop('finished_callback', None)

if cancel_callback is None:
# attempt to load signal handler.
# will return None if we are not in the main thread
cancel_callback = signal_handler()
finished_callback = kwargs.pop('finished_callback', None)
cancel_callback, original_handlers = signal_handler()
if cancel_callback is not None:
# need to use finished_callback to restore signals
original_finished_callback = finished_callback

def restoring_finished_callback() -> None:
for signum, handler in original_handlers:
if handler is None:
# TODO this will silently fail to restore signal handlers set from outside Python
continue
signal.signal(signum, handler)
if original_finished_callback is not None:
original_finished_callback()

finished_callback = restoring_finished_callback

streamer = kwargs.pop('streamer', None)
if streamer:
Expand Down
12 changes: 10 additions & 2 deletions src/ansible_runner/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from codecs import StreamReaderWriter
from collections.abc import Callable, Iterable, MutableMapping
from io import StringIO
from types import FrameType
from typing import Any, Iterator

from ansible_runner.exceptions import ConfigurationError
Expand Down Expand Up @@ -503,7 +504,10 @@ def get_executable_path(name: str) -> str:
return exec_path


def signal_handler() -> Callable[[], bool] | None:
_HANDLER = Callable[[int, FrameType | None], Any] | int | signal.Handlers | None


def signal_handler() -> tuple[Callable[[], bool], list[tuple[signal.Signal, _HANDLER]]] | None:
# Only the main thread is allowed to set a new signal handler
# pylint: disable=W4902
if threading.current_thread() is not threading.main_thread():
Expand All @@ -516,7 +520,11 @@ def _handler(number, frame):
# pylint: disable=W0613
signal_event.set()

original_handlers: list[tuple[signal.Signal, _HANDLER]] = []

original_handlers.append((signal.SIGTERM, signal.getsignal(signal.SIGTERM)))
signal.signal(signal.SIGTERM, _handler)
original_handlers.append((signal.SIGINT, signal.getsignal(signal.SIGINT)))
signal.signal(signal.SIGINT, _handler)

return signal_event.is_set
return signal_event.is_set, original_handlers
7 changes: 5 additions & 2 deletions test/unit/utils/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,8 @@ def is_set(self):
mocker.patch('ansible_runner.utils.threading.Event', MockEvent)
mock_signal = mocker.patch('ansible_runner.utils.signal.signal')

assert signal_handler()() is False
callback, _ = signal_handler()
assert callback() is False
assert mock_signal.call_args_list[0][0][0] == signal.SIGTERM
assert mock_signal.call_args_list[1][0][0] == signal.SIGINT

Expand All @@ -300,7 +301,9 @@ def test_signal_handler_outside_main_thread(mocker):
mocker.patch('ansible_runner.utils.threading.main_thread', return_value='thread0')
mocker.patch('ansible_runner.utils.threading.current_thread', return_value='thread1')

assert signal_handler() is None
callback, original_handlers = signal_handler()
assert callback() is None
assert len(original_handlers) == 0


def test_signal_handler_set(mocker):
Expand Down
Loading