Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WindowsSelectorEventLoopPolicy doesn't raise KeyboardInterrupt immediately #101166

Open
davetapley opened this issue Jan 19, 2023 · 3 comments
Open
Labels
OS-windows topic-asyncio type-bug An unexpected behavior, bug, or error

Comments

@davetapley
Copy link

davetapley commented Jan 19, 2023

Bug report

Run the snippet below and Ctrl+C,
observe interrupted will only be printed after ten seconds.

Comment out set_event_loop_policy and repeat,
observe interrupted is printed immediately.

import asyncio
import sys

if sys.platform == 'win32':
   asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

async def sleep():
    print('sleeping')
    await asyncio.sleep(10)
    print('done')

try:
    asyncio.run(sleep())
except KeyboardInterrupt:
    print('interrupted')

For context: I need WindowsSelectorEventLoopPolicy to mitigate this error with pyzmq:

RuntimeError: Proactor event loop does not implement add_reader family of methods required for zmq. zmq will work 
with proactor if tornado >= 6.1 can be found. Use `asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy())` or install 'tornado>=6.1' to avoid this error.

Your environment

  • CPython versions tested on: 3.9.10
  • Operating system and architecture: Windows 10, 64-bit
@davetapley davetapley added the type-bug An unexpected behavior, bug, or error label Jan 19, 2023
@github-project-automation github-project-automation bot moved this to Todo in asyncio Jan 20, 2023
@gvanrossum
Copy link
Member

Can you repro this in 3.11? Or in main? And why can't you use the other mitigation (tornado >= 6.1)?

@eryksun
Copy link
Contributor

eryksun commented Jan 20, 2023

The main thread is blocked in a Winsock select() call. Since the interpreter calls signal handlers on the main thread, the runner's SIGINT handler (i.e. asyncio.runners.Runner._on_sigint()) won't be called until the interpreter resumes executing on the main thread.

You can complete the select() call using the event loop's _write_to_self() method, which is allowed to be called from another thread. A native Windows console control handler is always called on a new thread. For example:

import asyncio
import sys

if sys.platform == 'win32':
    import ctypes
    import signal

    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    runner = None

    @ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_ulong) 
    def ctrl_handler(event):
        if event == signal.CTRL_C_EVENT:
            if runner is not None:
                loop = runner.get_loop()
                if loop is not None: 
                    loop._write_to_self()
        # Chain to the next registered control handler, which is probably 
        # the C runtime library's SIGINT/SIGBREAK implementation.
        return False

    kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
    kernel32.SetConsoleCtrlHandler(ctrl_handler, True)

async def sleep():
    print('sleeping')
    await asyncio.sleep(10)
    print('done')

try:
    with asyncio.Runner() as runner:
        runner.run(sleep())
except KeyboardInterrupt:
    print('interrupted')

@kumaraditya303
Copy link
Contributor

Looks like a duplicate of #77373 but I haven't verified it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
OS-windows topic-asyncio type-bug An unexpected behavior, bug, or error
Projects
Status: Todo
Development

No branches or pull requests

4 participants