Skip to content

Commit

Permalink
bpo-23057: add loop self socket as wakeup fd for signals (python#11135)
Browse files Browse the repository at this point in the history
  • Loading branch information
vladima authored and asvetlov committed Dec 18, 2018
1 parent e3666fc commit b5c8cfa
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 6 deletions.
5 changes: 4 additions & 1 deletion Lib/asyncio/proactor_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import os
import socket
import warnings
import signal

from . import base_events
from . import constants
Expand Down Expand Up @@ -489,6 +490,8 @@ def __init__(self, proactor):
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):
Expand Down Expand Up @@ -529,6 +532,7 @@ def close(self):
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.
Expand Down Expand Up @@ -613,7 +617,6 @@ def _make_self_pipe(self):
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:
Expand Down
10 changes: 10 additions & 0 deletions Lib/asyncio/windows_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,16 @@ def __init__(self, proactor=None):
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
Expand Down
63 changes: 63 additions & 0 deletions Lib/test/test_asyncio/test_ctrl_c_in_proactor_loop_helper.py
Original file line number Diff line number Diff line change
@@ -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()
10 changes: 5 additions & 5 deletions Lib/test/test_asyncio/test_proactor_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -737,19 +737,19 @@ def setUp(self):

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):
Expand Down
21 changes: 21 additions & 0 deletions Lib/test/test_asyncio/test_windows_events.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import os
import signal
import socket
import sys
import subprocess
import time
import unittest
from unittest import mock

Expand All @@ -13,6 +16,7 @@
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():
Expand All @@ -33,6 +37,23 @@ def data_received(self, data):
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):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Unblock Proactor event loop when keyboard interrupt is received on Windows

0 comments on commit b5c8cfa

Please sign in to comment.