Skip to content

Commit 0587810

Browse files
authored
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.
1 parent 2cd70ff commit 0587810

File tree

5 files changed

+66
-8
lines changed

5 files changed

+66
-8
lines changed

Lib/asyncio/proactor_events.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,12 @@ def __init__(self, proactor):
635635
self._make_self_pipe()
636636
if threading.current_thread() is threading.main_thread():
637637
# wakeup fd can only be installed to a file descriptor from the main thread
638-
signal.set_wakeup_fd(self._csock.fileno())
638+
oldfd = signal.set_wakeup_fd(self._csock.fileno())
639+
if oldfd != -1:
640+
warnings.warn(
641+
"Signal wakeup fd was already set",
642+
ResourceWarning,
643+
source=self)
639644

640645
def _make_socket_transport(self, sock, protocol, waiter=None,
641646
extra=None, server=None):
@@ -684,7 +689,12 @@ def close(self):
684689
return
685690

686691
if threading.current_thread() is threading.main_thread():
687-
signal.set_wakeup_fd(-1)
692+
oldfd = signal.set_wakeup_fd(-1)
693+
if oldfd != self._csock.fileno():
694+
warnings.warn(
695+
"Got unexpected signal wakeup fd",
696+
ResourceWarning,
697+
source=self)
688698
# Call these methods before closing the event loop (before calling
689699
# BaseEventLoop.close), because they can schedule callbacks with
690700
# call_soon(), which is forbidden when the event loop is closed.

Lib/asyncio/unix_events.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ def __init__(self, selector=None):
6565
self._signal_handlers = {}
6666

6767
def close(self):
68-
super().close()
68+
# remove signal handlers first to verify
69+
# the loop's signal handling setup has not
70+
# been tampered with
6971
if not sys.is_finalizing():
7072
for sig in list(self._signal_handlers):
7173
self.remove_signal_handler(sig)
@@ -77,6 +79,7 @@ def close(self):
7779
ResourceWarning,
7880
source=self)
7981
self._signal_handlers.clear()
82+
super().close()
8083

8184
def _process_self_data(self, data):
8285
for signum in data:
@@ -102,7 +105,12 @@ def add_signal_handler(self, sig, callback, *args):
102105
# main thread. By calling it early we ensure that an
103106
# event loop running in another thread cannot add a signal
104107
# handler.
105-
signal.set_wakeup_fd(self._csock.fileno())
108+
oldfd = signal.set_wakeup_fd(self._csock.fileno())
109+
if oldfd != -1 and oldfd != self._csock.fileno():
110+
warnings.warn(
111+
"Signal wakeup fd was already set",
112+
ResourceWarning,
113+
source=self)
106114
except (ValueError, OSError) as exc:
107115
raise RuntimeError(str(exc))
108116

@@ -166,7 +174,12 @@ def remove_signal_handler(self, sig):
166174

167175
if not self._signal_handlers:
168176
try:
169-
signal.set_wakeup_fd(-1)
177+
oldfd = signal.set_wakeup_fd(-1)
178+
if oldfd != -1 and oldfd != self._csock.fileno():
179+
warnings.warn(
180+
"Got unexpected signal wakeup fd",
181+
ResourceWarning,
182+
source=self)
170183
except (ValueError, OSError) as exc:
171184
logger.info('set_wakeup_fd(-1) failed: %s', exc)
172185

Lib/test/test_asyncio/test_proactor_events.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -720,8 +720,12 @@ def setUp(self):
720720
def test_ctor(self, socketpair):
721721
ssock, csock = socketpair.return_value = (
722722
mock.Mock(), mock.Mock())
723-
with mock.patch('signal.set_wakeup_fd'):
724-
loop = BaseProactorEventLoop(self.proactor)
723+
with mock.patch('signal.set_wakeup_fd') as set_wakeup_fd:
724+
set_wakeup_fd.return_value = -1000
725+
with self.assertWarnsRegex(
726+
ResourceWarning, 'Signal wakeup fd was already set'
727+
):
728+
loop = BaseProactorEventLoop(self.proactor)
725729
self.assertIs(loop._ssock, ssock)
726730
self.assertIs(loop._csock, csock)
727731
self.assertEqual(loop._internal_fds, 1)
@@ -740,7 +744,12 @@ def test_close_self_pipe(self):
740744

741745
def test_close(self):
742746
self.loop._close_self_pipe = mock.Mock()
743-
self.loop.close()
747+
with mock.patch('signal.set_wakeup_fd') as set_wakeup_fd:
748+
set_wakeup_fd.return_value = -1000
749+
with self.assertWarnsRegex(
750+
ResourceWarning, 'Got unexpected signal wakeup fd'
751+
):
752+
self.loop.close()
744753
self.assertTrue(self.loop._close_self_pipe.called)
745754
self.assertTrue(self.proactor.close.called)
746755
self.assertIsNone(self.loop._proactor)

Lib/test/test_asyncio/test_unix_events.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,17 @@ def test_add_signal_handler_setup_error(self, m_signal):
8888
self.loop.add_signal_handler,
8989
signal.SIGINT, lambda: True)
9090

91+
@mock.patch('asyncio.unix_events.signal')
92+
def test_add_signal_handler_setup_warn(self, m_signal):
93+
m_signal.NSIG = signal.NSIG
94+
m_signal.valid_signals = signal.valid_signals
95+
m_signal.set_wakeup_fd.return_value = -1000
96+
97+
with self.assertWarnsRegex(
98+
ResourceWarning, 'Signal wakeup fd was already set'
99+
):
100+
self.loop.add_signal_handler(signal.SIGINT, lambda: True)
101+
91102
@mock.patch('asyncio.unix_events.signal')
92103
def test_add_signal_handler_coroutine_error(self, m_signal):
93104
m_signal.NSIG = signal.NSIG
@@ -213,6 +224,19 @@ def test_remove_signal_handler_cleanup_error(self, m_logging, m_signal):
213224
self.loop.remove_signal_handler(signal.SIGHUP)
214225
self.assertTrue(m_logging.info)
215226

227+
@mock.patch('asyncio.unix_events.signal')
228+
def test_remove_signal_handler_cleanup_warn(self, m_signal):
229+
m_signal.NSIG = signal.NSIG
230+
m_signal.valid_signals = signal.valid_signals
231+
self.loop.add_signal_handler(signal.SIGHUP, lambda: True)
232+
233+
m_signal.set_wakeup_fd.return_value = -1000
234+
235+
with self.assertWarnsRegex(
236+
ResourceWarning, 'Got unexpected signal wakeup fd'
237+
):
238+
self.loop.remove_signal_handler(signal.SIGHUP)
239+
216240
@mock.patch('asyncio.unix_events.signal')
217241
def test_remove_signal_handler_error(self, m_signal):
218242
m_signal.NSIG = signal.NSIG
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Warn the user whenever asyncio event loops override a signal wake up file
2+
descriptor that was previously set.

0 commit comments

Comments
 (0)