bpo-23057: add loop self socket as wakeup fd for signals (#11135)
This commit is contained in:
parent
e3666fc8ef
commit
b5c8cfa1da
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Unblock Proactor event loop when keyboard interrupt is received on Windows
|
Loading…
Reference in New Issue