diff options
author | Vladimir Matveev <v2matveev@outlook.com> | 2018-12-18 21:56:17 (GMT) |
---|---|---|
committer | Andrew Svetlov <andrew.svetlov@gmail.com> | 2018-12-18 21:56:17 (GMT) |
commit | b5c8cfa1da17c6f3acac80a0afca7f7104fb9589 (patch) | |
tree | d725abe7cb749e02672c269d7edc7bbcfd8faab6 | |
parent | e3666fc8effb05b555121f4ab7388df59e21f8b4 (diff) | |
download | cpython-b5c8cfa1da17c6f3acac80a0afca7f7104fb9589.zip cpython-b5c8cfa1da17c6f3acac80a0afca7f7104fb9589.tar.gz cpython-b5c8cfa1da17c6f3acac80a0afca7f7104fb9589.tar.bz2 |
bpo-23057: add loop self socket as wakeup fd for signals (#11135)
-rw-r--r-- | Lib/asyncio/proactor_events.py | 5 | ||||
-rw-r--r-- | Lib/asyncio/windows_events.py | 10 | ||||
-rw-r--r-- | Lib/test/test_asyncio/test_ctrl_c_in_proactor_loop_helper.py | 63 | ||||
-rw-r--r-- | Lib/test/test_asyncio/test_proactor_events.py | 10 | ||||
-rw-r--r-- | Lib/test/test_asyncio/test_windows_events.py | 21 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2018-12-12-16-24-55.bpo-23057.OB4Z1Y.rst | 1 |
6 files changed, 104 insertions, 6 deletions
diff --git a/Lib/asyncio/proactor_events.py b/Lib/asyncio/proactor_events.py index 69d96a8..9b9e0aa 100644 --- a/Lib/asyncio/proactor_events.py +++ b/Lib/asyncio/proactor_events.py @@ -10,6 +10,7 @@ import io import os import socket import warnings +import signal from . import base_events from . import constants @@ -489,6 +490,8 @@ class BaseProactorEventLoop(base_events.BaseEventLoop): self._accept_futures = {} # socket file descriptor => Future proactor.set_loop(self) self._make_self_pipe() + self_no = self._csock.fileno() + signal.set_wakeup_fd(self_no) def _make_socket_transport(self, sock, protocol, waiter=None, extra=None, server=None): @@ -529,6 +532,7 @@ class BaseProactorEventLoop(base_events.BaseEventLoop): if self.is_closed(): return + signal.set_wakeup_fd(-1) # Call these methods before closing the event loop (before calling # BaseEventLoop.close), because they can schedule callbacks with # call_soon(), which is forbidden when the event loop is closed. @@ -613,7 +617,6 @@ class BaseProactorEventLoop(base_events.BaseEventLoop): self._ssock.setblocking(False) self._csock.setblocking(False) self._internal_fds += 1 - self.call_soon(self._loop_self_reading) def _loop_self_reading(self, f=None): try: diff --git a/Lib/asyncio/windows_events.py b/Lib/asyncio/windows_events.py index 772ddf4..33ffaf9 100644 --- a/Lib/asyncio/windows_events.py +++ b/Lib/asyncio/windows_events.py @@ -308,6 +308,16 @@ class ProactorEventLoop(proactor_events.BaseProactorEventLoop): proactor = IocpProactor() super().__init__(proactor) + def run_forever(self): + try: + assert self._self_reading_future is None + self.call_soon(self._loop_self_reading) + super().run_forever() + finally: + if self._self_reading_future is not None: + self._self_reading_future.cancel() + self._self_reading_future = None + async def create_pipe_connection(self, protocol_factory, address): f = self._proactor.connect_pipe(address) pipe = await f diff --git a/Lib/test/test_asyncio/test_ctrl_c_in_proactor_loop_helper.py b/Lib/test/test_asyncio/test_ctrl_c_in_proactor_loop_helper.py new file mode 100644 index 0000000..9aeb58a --- /dev/null +++ b/Lib/test/test_asyncio/test_ctrl_c_in_proactor_loop_helper.py @@ -0,0 +1,63 @@ +import sys + + +def do_in_child_process(): + import asyncio + + asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) + l = asyncio.get_event_loop() + + def step(n): + try: + print(n) + sys.stdout.flush() + l.run_forever() + sys.exit(100) + except KeyboardInterrupt: + # ok + pass + except: + # error - use default exit code + sys.exit(200) + + step(1) + step(2) + sys.exit(255) + + +def do_in_main_process(): + import os + import signal + import subprocess + import time + from test.support.script_helper import spawn_python + + ok = False + + def step(p, expected): + s = p.stdout.readline() + if s != expected: + raise Exception(f"Unexpected line: got {s}, expected '{expected}'") + # ensure that child process gets to run_forever + time.sleep(0.5) + os.kill(p.pid, signal.CTRL_C_EVENT) + + with spawn_python(__file__, "--child") as p: + try: + # ignore ctrl-c in current process + signal.signal(signal.SIGINT, signal.SIG_IGN) + step(p, b"1\r\n") + step(p, b"2\r\n") + exit_code = p.wait(timeout=5) + ok = exit_code = 255 + except Exception as e: + sys.stderr.write(repr(e)) + p.kill() + sys.exit(255 if ok else 1) + + +if __name__ == "__main__": + if len(sys.argv) == 1: + do_in_main_process() + else: + do_in_child_process() diff --git a/Lib/test/test_asyncio/test_proactor_events.py b/Lib/test/test_asyncio/test_proactor_events.py index afc4c19..5952ccc 100644 --- a/Lib/test/test_asyncio/test_proactor_events.py +++ b/Lib/test/test_asyncio/test_proactor_events.py @@ -737,19 +737,19 @@ class BaseProactorEventLoopTests(test_utils.TestCase): with mock.patch('asyncio.proactor_events.socket.socketpair', return_value=(self.ssock, self.csock)): - self.loop = BaseProactorEventLoop(self.proactor) + with mock.patch('signal.set_wakeup_fd'): + self.loop = BaseProactorEventLoop(self.proactor) self.set_event_loop(self.loop) - @mock.patch.object(BaseProactorEventLoop, 'call_soon') @mock.patch('asyncio.proactor_events.socket.socketpair') - def test_ctor(self, socketpair, call_soon): + def test_ctor(self, socketpair): ssock, csock = socketpair.return_value = ( mock.Mock(), mock.Mock()) - loop = BaseProactorEventLoop(self.proactor) + with mock.patch('signal.set_wakeup_fd'): + loop = BaseProactorEventLoop(self.proactor) self.assertIs(loop._ssock, ssock) self.assertIs(loop._csock, csock) self.assertEqual(loop._internal_fds, 1) - call_soon.assert_called_with(loop._loop_self_reading) loop.close() def test_close_self_pipe(self): diff --git a/Lib/test/test_asyncio/test_windows_events.py b/Lib/test/test_asyncio/test_windows_events.py index 8f4c50e..05d875a 100644 --- a/Lib/test/test_asyncio/test_windows_events.py +++ b/Lib/test/test_asyncio/test_windows_events.py @@ -1,6 +1,9 @@ import os +import signal import socket import sys +import subprocess +import time import unittest from unittest import mock @@ -13,6 +16,7 @@ import _winapi import asyncio from asyncio import windows_events from test.test_asyncio import utils as test_utils +from test.support.script_helper import spawn_python def tearDownModule(): @@ -33,6 +37,23 @@ class UpperProto(asyncio.Protocol): self.trans.close() +class ProactorLoopCtrlC(test_utils.TestCase): + def test_ctrl_c(self): + from .test_ctrl_c_in_proactor_loop_helper import __file__ as f + + # ctrl-c will be sent to all processes that share the same console + # in order to isolate the effect of raising ctrl-c we'll create + # a process with a new console + flags = subprocess.CREATE_NEW_CONSOLE + with spawn_python(f, creationflags=flags) as p: + try: + exit_code = p.wait(timeout=5) + self.assertEqual(exit_code, 255) + except: + p.kill() + raise + + class ProactorTests(test_utils.TestCase): def setUp(self): diff --git a/Misc/NEWS.d/next/Library/2018-12-12-16-24-55.bpo-23057.OB4Z1Y.rst b/Misc/NEWS.d/next/Library/2018-12-12-16-24-55.bpo-23057.OB4Z1Y.rst new file mode 100644 index 0000000..51b727d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-12-12-16-24-55.bpo-23057.OB4Z1Y.rst @@ -0,0 +1 @@ +Unblock Proactor event loop when keyboard interrupt is received on Windows |