Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b5c8cfa

Browse files
vladimaasvetlov
authored andcommitted
bpo-23057: add loop self socket as wakeup fd for signals (#11135)
1 parent e3666fc commit b5c8cfa

6 files changed

Lines changed: 104 additions & 6 deletions

File tree

Lib/asyncio/proactor_events.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import os
1111
import socket
1212
import warnings
13+
import signal
1314

1415
from . import base_events
1516
from . import constants
@@ -489,6 +490,8 @@ def __init__(self, proactor):
489490
self._accept_futures = {} # socket file descriptor => Future
490491
proactor.set_loop(self)
491492
self._make_self_pipe()
493+
self_no = self._csock.fileno()
494+
signal.set_wakeup_fd(self_no)
492495

493496
def _make_socket_transport(self, sock, protocol, waiter=None,
494497
extra=None, server=None):
@@ -529,6 +532,7 @@ def close(self):
529532
if self.is_closed():
530533
return
531534

535+
signal.set_wakeup_fd(-1)
532536
# Call these methods before closing the event loop (before calling
533537
# BaseEventLoop.close), because they can schedule callbacks with
534538
# call_soon(), which is forbidden when the event loop is closed.
@@ -613,7 +617,6 @@ def _make_self_pipe(self):
613617
self._ssock.setblocking(False)
614618
self._csock.setblocking(False)
615619
self._internal_fds += 1
616-
self.call_soon(self._loop_self_reading)
617620

618621
def _loop_self_reading(self, f=None):
619622
try:

Lib/asyncio/windows_events.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,16 @@ def __init__(self, proactor=None):
308308
proactor = IocpProactor()
309309
super().__init__(proactor)
310310

311+
def run_forever(self):
312+
try:
313+
assert self._self_reading_future is None
314+
self.call_soon(self._loop_self_reading)
315+
super().run_forever()
316+
finally:
317+
if self._self_reading_future is not None:
318+
self._self_reading_future.cancel()
319+
self._self_reading_future = None
320+
311321
async def create_pipe_connection(self, protocol_factory, address):
312322
f = self._proactor.connect_pipe(address)
313323
pipe = await f
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import sys
2+
3+
4+
def do_in_child_process():
5+
import asyncio
6+
7+
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
8+
l = asyncio.get_event_loop()
9+
10+
def step(n):
11+
try:
12+
print(n)
13+
sys.stdout.flush()
14+
l.run_forever()
15+
sys.exit(100)
16+
except KeyboardInterrupt:
17+
# ok
18+
pass
19+
except:
20+
# error - use default exit code
21+
sys.exit(200)
22+
23+
step(1)
24+
step(2)
25+
sys.exit(255)
26+
27+
28+
def do_in_main_process():
29+
import os
30+
import signal
31+
import subprocess
32+
import time
33+
from test.support.script_helper import spawn_python
34+
35+
ok = False
36+
37+
def step(p, expected):
38+
s = p.stdout.readline()
39+
if s != expected:
40+
raise Exception(f"Unexpected line: got {s}, expected '{expected}'")
41+
# ensure that child process gets to run_forever
42+
time.sleep(0.5)
43+
os.kill(p.pid, signal.CTRL_C_EVENT)
44+
45+
with spawn_python(__file__, "--child") as p:
46+
try:
47+
# ignore ctrl-c in current process
48+
signal.signal(signal.SIGINT, signal.SIG_IGN)
49+
step(p, b"1\r\n")
50+
step(p, b"2\r\n")
51+
exit_code = p.wait(timeout=5)
52+
ok = exit_code = 255
53+
except Exception as e:
54+
sys.stderr.write(repr(e))
55+
p.kill()
56+
sys.exit(255 if ok else 1)
57+
58+
59+
if __name__ == "__main__":
60+
if len(sys.argv) == 1:
61+
do_in_main_process()
62+
else:
63+
do_in_child_process()

Lib/test/test_asyncio/test_proactor_events.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -737,19 +737,19 @@ def setUp(self):
737737

738738
with mock.patch('asyncio.proactor_events.socket.socketpair',
739739
return_value=(self.ssock, self.csock)):
740-
self.loop = BaseProactorEventLoop(self.proactor)
740+
with mock.patch('signal.set_wakeup_fd'):
741+
self.loop = BaseProactorEventLoop(self.proactor)
741742
self.set_event_loop(self.loop)
742743

743-
@mock.patch.object(BaseProactorEventLoop, 'call_soon')
744744
@mock.patch('asyncio.proactor_events.socket.socketpair')
745-
def test_ctor(self, socketpair, call_soon):
745+
def test_ctor(self, socketpair):
746746
ssock, csock = socketpair.return_value = (
747747
mock.Mock(), mock.Mock())
748-
loop = BaseProactorEventLoop(self.proactor)
748+
with mock.patch('signal.set_wakeup_fd'):
749+
loop = BaseProactorEventLoop(self.proactor)
749750
self.assertIs(loop._ssock, ssock)
750751
self.assertIs(loop._csock, csock)
751752
self.assertEqual(loop._internal_fds, 1)
752-
call_soon.assert_called_with(loop._loop_self_reading)
753753
loop.close()
754754

755755
def test_close_self_pipe(self):

Lib/test/test_asyncio/test_windows_events.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import os
2+
import signal
23
import socket
34
import sys
5+
import subprocess
6+
import time
47
import unittest
58
from unittest import mock
69

@@ -13,6 +16,7 @@
1316
import asyncio
1417
from asyncio import windows_events
1518
from test.test_asyncio import utils as test_utils
19+
from test.support.script_helper import spawn_python
1620

1721

1822
def tearDownModule():
@@ -33,6 +37,23 @@ def data_received(self, data):
3337
self.trans.close()
3438

3539

40+
class ProactorLoopCtrlC(test_utils.TestCase):
41+
def test_ctrl_c(self):
42+
from .test_ctrl_c_in_proactor_loop_helper import __file__ as f
43+
44+
# ctrl-c will be sent to all processes that share the same console
45+
# in order to isolate the effect of raising ctrl-c we'll create
46+
# a process with a new console
47+
flags = subprocess.CREATE_NEW_CONSOLE
48+
with spawn_python(f, creationflags=flags) as p:
49+
try:
50+
exit_code = p.wait(timeout=5)
51+
self.assertEqual(exit_code, 255)
52+
except:
53+
p.kill()
54+
raise
55+
56+
3657
class ProactorTests(test_utils.TestCase):
3758

3859
def setUp(self):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Unblock Proactor event loop when keyboard interrupt is received on Windows

0 commit comments

Comments
 (0)