summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVladimir Matveev <v2matveev@outlook.com>2018-12-18 21:56:17 (GMT)
committerAndrew Svetlov <andrew.svetlov@gmail.com>2018-12-18 21:56:17 (GMT)
commitb5c8cfa1da17c6f3acac80a0afca7f7104fb9589 (patch)
treed725abe7cb749e02672c269d7edc7bbcfd8faab6
parente3666fc8effb05b555121f4ab7388df59e21f8b4 (diff)
downloadcpython-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.py5
-rw-r--r--Lib/asyncio/windows_events.py10
-rw-r--r--Lib/test/test_asyncio/test_ctrl_c_in_proactor_loop_helper.py63
-rw-r--r--Lib/test/test_asyncio/test_proactor_events.py10
-rw-r--r--Lib/test/test_asyncio/test_windows_events.py21
-rw-r--r--Misc/NEWS.d/next/Library/2018-12-12-16-24-55.bpo-23057.OB4Z1Y.rst1
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