mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
gh-87079: Warn on unintended signal wakeup fd override in asyncio
(#96807)
Warn on loop initialization, when setting the wakeup fd disturbs a previously set wakeup fd, and on loop closing, when upon resetting the wakeup fd, we find it has been changed by someone else.
This commit is contained in:
parent
2cd70ffb3f
commit
0587810698
5 changed files with 66 additions and 8 deletions
|
@ -635,7 +635,12 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
|
||||||
self._make_self_pipe()
|
self._make_self_pipe()
|
||||||
if threading.current_thread() is threading.main_thread():
|
if threading.current_thread() is threading.main_thread():
|
||||||
# wakeup fd can only be installed to a file descriptor from the main thread
|
# wakeup fd can only be installed to a file descriptor from the main thread
|
||||||
signal.set_wakeup_fd(self._csock.fileno())
|
oldfd = signal.set_wakeup_fd(self._csock.fileno())
|
||||||
|
if oldfd != -1:
|
||||||
|
warnings.warn(
|
||||||
|
"Signal wakeup fd was already set",
|
||||||
|
ResourceWarning,
|
||||||
|
source=self)
|
||||||
|
|
||||||
def _make_socket_transport(self, sock, protocol, waiter=None,
|
def _make_socket_transport(self, sock, protocol, waiter=None,
|
||||||
extra=None, server=None):
|
extra=None, server=None):
|
||||||
|
@ -684,7 +689,12 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
|
||||||
return
|
return
|
||||||
|
|
||||||
if threading.current_thread() is threading.main_thread():
|
if threading.current_thread() is threading.main_thread():
|
||||||
signal.set_wakeup_fd(-1)
|
oldfd = signal.set_wakeup_fd(-1)
|
||||||
|
if oldfd != self._csock.fileno():
|
||||||
|
warnings.warn(
|
||||||
|
"Got unexpected signal wakeup fd",
|
||||||
|
ResourceWarning,
|
||||||
|
source=self)
|
||||||
# Call these methods before closing the event loop (before calling
|
# Call these methods before closing the event loop (before calling
|
||||||
# BaseEventLoop.close), because they can schedule callbacks with
|
# BaseEventLoop.close), because they can schedule callbacks with
|
||||||
# call_soon(), which is forbidden when the event loop is closed.
|
# call_soon(), which is forbidden when the event loop is closed.
|
||||||
|
|
|
@ -65,7 +65,9 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
|
||||||
self._signal_handlers = {}
|
self._signal_handlers = {}
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
super().close()
|
# remove signal handlers first to verify
|
||||||
|
# the loop's signal handling setup has not
|
||||||
|
# been tampered with
|
||||||
if not sys.is_finalizing():
|
if not sys.is_finalizing():
|
||||||
for sig in list(self._signal_handlers):
|
for sig in list(self._signal_handlers):
|
||||||
self.remove_signal_handler(sig)
|
self.remove_signal_handler(sig)
|
||||||
|
@ -77,6 +79,7 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
|
||||||
ResourceWarning,
|
ResourceWarning,
|
||||||
source=self)
|
source=self)
|
||||||
self._signal_handlers.clear()
|
self._signal_handlers.clear()
|
||||||
|
super().close()
|
||||||
|
|
||||||
def _process_self_data(self, data):
|
def _process_self_data(self, data):
|
||||||
for signum in data:
|
for signum in data:
|
||||||
|
@ -102,7 +105,12 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
|
||||||
# main thread. By calling it early we ensure that an
|
# main thread. By calling it early we ensure that an
|
||||||
# event loop running in another thread cannot add a signal
|
# event loop running in another thread cannot add a signal
|
||||||
# handler.
|
# handler.
|
||||||
signal.set_wakeup_fd(self._csock.fileno())
|
oldfd = signal.set_wakeup_fd(self._csock.fileno())
|
||||||
|
if oldfd != -1 and oldfd != self._csock.fileno():
|
||||||
|
warnings.warn(
|
||||||
|
"Signal wakeup fd was already set",
|
||||||
|
ResourceWarning,
|
||||||
|
source=self)
|
||||||
except (ValueError, OSError) as exc:
|
except (ValueError, OSError) as exc:
|
||||||
raise RuntimeError(str(exc))
|
raise RuntimeError(str(exc))
|
||||||
|
|
||||||
|
@ -166,7 +174,12 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
|
||||||
|
|
||||||
if not self._signal_handlers:
|
if not self._signal_handlers:
|
||||||
try:
|
try:
|
||||||
signal.set_wakeup_fd(-1)
|
oldfd = signal.set_wakeup_fd(-1)
|
||||||
|
if oldfd != -1 and oldfd != self._csock.fileno():
|
||||||
|
warnings.warn(
|
||||||
|
"Got unexpected signal wakeup fd",
|
||||||
|
ResourceWarning,
|
||||||
|
source=self)
|
||||||
except (ValueError, OSError) as exc:
|
except (ValueError, OSError) as exc:
|
||||||
logger.info('set_wakeup_fd(-1) failed: %s', exc)
|
logger.info('set_wakeup_fd(-1) failed: %s', exc)
|
||||||
|
|
||||||
|
|
|
@ -720,7 +720,11 @@ class BaseProactorEventLoopTests(test_utils.TestCase):
|
||||||
def test_ctor(self, socketpair):
|
def test_ctor(self, socketpair):
|
||||||
ssock, csock = socketpair.return_value = (
|
ssock, csock = socketpair.return_value = (
|
||||||
mock.Mock(), mock.Mock())
|
mock.Mock(), mock.Mock())
|
||||||
with mock.patch('signal.set_wakeup_fd'):
|
with mock.patch('signal.set_wakeup_fd') as set_wakeup_fd:
|
||||||
|
set_wakeup_fd.return_value = -1000
|
||||||
|
with self.assertWarnsRegex(
|
||||||
|
ResourceWarning, 'Signal wakeup fd was already set'
|
||||||
|
):
|
||||||
loop = BaseProactorEventLoop(self.proactor)
|
loop = BaseProactorEventLoop(self.proactor)
|
||||||
self.assertIs(loop._ssock, ssock)
|
self.assertIs(loop._ssock, ssock)
|
||||||
self.assertIs(loop._csock, csock)
|
self.assertIs(loop._csock, csock)
|
||||||
|
@ -740,6 +744,11 @@ class BaseProactorEventLoopTests(test_utils.TestCase):
|
||||||
|
|
||||||
def test_close(self):
|
def test_close(self):
|
||||||
self.loop._close_self_pipe = mock.Mock()
|
self.loop._close_self_pipe = mock.Mock()
|
||||||
|
with mock.patch('signal.set_wakeup_fd') as set_wakeup_fd:
|
||||||
|
set_wakeup_fd.return_value = -1000
|
||||||
|
with self.assertWarnsRegex(
|
||||||
|
ResourceWarning, 'Got unexpected signal wakeup fd'
|
||||||
|
):
|
||||||
self.loop.close()
|
self.loop.close()
|
||||||
self.assertTrue(self.loop._close_self_pipe.called)
|
self.assertTrue(self.loop._close_self_pipe.called)
|
||||||
self.assertTrue(self.proactor.close.called)
|
self.assertTrue(self.proactor.close.called)
|
||||||
|
|
|
@ -88,6 +88,17 @@ class SelectorEventLoopSignalTests(test_utils.TestCase):
|
||||||
self.loop.add_signal_handler,
|
self.loop.add_signal_handler,
|
||||||
signal.SIGINT, lambda: True)
|
signal.SIGINT, lambda: True)
|
||||||
|
|
||||||
|
@mock.patch('asyncio.unix_events.signal')
|
||||||
|
def test_add_signal_handler_setup_warn(self, m_signal):
|
||||||
|
m_signal.NSIG = signal.NSIG
|
||||||
|
m_signal.valid_signals = signal.valid_signals
|
||||||
|
m_signal.set_wakeup_fd.return_value = -1000
|
||||||
|
|
||||||
|
with self.assertWarnsRegex(
|
||||||
|
ResourceWarning, 'Signal wakeup fd was already set'
|
||||||
|
):
|
||||||
|
self.loop.add_signal_handler(signal.SIGINT, lambda: True)
|
||||||
|
|
||||||
@mock.patch('asyncio.unix_events.signal')
|
@mock.patch('asyncio.unix_events.signal')
|
||||||
def test_add_signal_handler_coroutine_error(self, m_signal):
|
def test_add_signal_handler_coroutine_error(self, m_signal):
|
||||||
m_signal.NSIG = signal.NSIG
|
m_signal.NSIG = signal.NSIG
|
||||||
|
@ -213,6 +224,19 @@ class SelectorEventLoopSignalTests(test_utils.TestCase):
|
||||||
self.loop.remove_signal_handler(signal.SIGHUP)
|
self.loop.remove_signal_handler(signal.SIGHUP)
|
||||||
self.assertTrue(m_logging.info)
|
self.assertTrue(m_logging.info)
|
||||||
|
|
||||||
|
@mock.patch('asyncio.unix_events.signal')
|
||||||
|
def test_remove_signal_handler_cleanup_warn(self, m_signal):
|
||||||
|
m_signal.NSIG = signal.NSIG
|
||||||
|
m_signal.valid_signals = signal.valid_signals
|
||||||
|
self.loop.add_signal_handler(signal.SIGHUP, lambda: True)
|
||||||
|
|
||||||
|
m_signal.set_wakeup_fd.return_value = -1000
|
||||||
|
|
||||||
|
with self.assertWarnsRegex(
|
||||||
|
ResourceWarning, 'Got unexpected signal wakeup fd'
|
||||||
|
):
|
||||||
|
self.loop.remove_signal_handler(signal.SIGHUP)
|
||||||
|
|
||||||
@mock.patch('asyncio.unix_events.signal')
|
@mock.patch('asyncio.unix_events.signal')
|
||||||
def test_remove_signal_handler_error(self, m_signal):
|
def test_remove_signal_handler_error(self, m_signal):
|
||||||
m_signal.NSIG = signal.NSIG
|
m_signal.NSIG = signal.NSIG
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Warn the user whenever asyncio event loops override a signal wake up file
|
||||||
|
descriptor that was previously set.
|
Loading…
Add table
Add a link
Reference in a new issue