From 15c68ea1185c37b124a958377be50c9f6dec1211 Mon Sep 17 00:00:00 2001
From: Andrew Svetlov <andrew.svetlov@gmail.com>
Date: Fri, 25 Mar 2022 01:27:59 +0200
Subject: [PATCH 01/20] bpo-39622: Interrupt the main asyncio task on Ctrl+C

---
 Lib/asyncio/runners.py                        | 40 ++++++++++++-
 Lib/test/test_asyncio/test_runners.py         | 59 ++++++++++++++++++-
 .../2022-03-25-01-27-25.bpo-39622.ieBIMp.rst  |  1 +
 3 files changed, 98 insertions(+), 2 deletions(-)
 create mode 100644 Misc/NEWS.d/next/Library/2022-03-25-01-27-25.bpo-39622.ieBIMp.rst

diff --git a/Lib/asyncio/runners.py b/Lib/asyncio/runners.py
index 975509c7d645d58..33a6e9825e2a2f3 100644
--- a/Lib/asyncio/runners.py
+++ b/Lib/asyncio/runners.py
@@ -2,8 +2,14 @@
 
 import contextvars
 import enum
+import functools
+import threading
+import platform
+import signal
+import sys
 from . import coroutines
 from . import events
+from . import exceptions
 from . import tasks
 
 
@@ -47,6 +53,7 @@ def __init__(self, *, debug=None, factory=None):
         self._factory = factory
         self._loop = None
         self._context = None
+        self._interrunt_count = 0
 
     def __enter__(self):
         self._lazy_init()
@@ -89,7 +96,32 @@ def run(self, coro, *, context=None):
         if context is None:
             context = self._context
         task = self._loop.create_task(coro, context=context)
-        return self._loop.run_until_complete(task)
+
+        if sys.platform == "win32":
+            signum = signal.SIGBREAK
+        else:
+            signum = signal.SIGINT
+        if (threading.current_thread() is threading.main_thread()
+            and signal.getsignal(signum) is signal.default_int_handler
+        ):
+            sigint_handler = functools.partial(self._on_sigint, main_task=task)
+            signal.signal(signum, sigint_handler)
+        else:
+            sigint_handler = None
+
+        self._interrunt_count = 0
+        try:
+            return self._loop.run_until_complete(task)
+        except exceptions.CancelledError:
+            if self._interrunt_count > 0 and task.uncancel() == 0:
+                raise KeyboardInterrupt()
+            else:
+                raise  # CancelledError
+        finally:
+            if (sigint_handler is not None
+                and signal.getsignal(signum) is sigint_handler
+            ):
+                signal.signal(signum, signal.default_int_handler)
 
     def _lazy_init(self):
         if self._state is _State.CLOSED:
@@ -105,6 +137,12 @@ def _lazy_init(self):
         self._context = contextvars.copy_context()
         self._state = _State.INITIALIZED
 
+    def _on_sigint(self, signum, frame, main_task):
+        self._interrunt_count += 1
+        if self._interrunt_count == 1:
+            main_task.cancel()
+            return
+        raise KeyboardInterrupt()
 
 
 def run(main, *, debug=None):
diff --git a/Lib/test/test_asyncio/test_runners.py b/Lib/test/test_asyncio/test_runners.py
index 2919412ab81db16..ff2f422ca45647d 100644
--- a/Lib/test/test_asyncio/test_runners.py
+++ b/Lib/test/test_asyncio/test_runners.py
@@ -1,7 +1,9 @@
+import _thread
 import asyncio
 import contextvars
 import gc
 import re
+import threading
 import unittest
 
 from unittest import mock
@@ -12,6 +14,10 @@ def tearDownModule():
     asyncio.set_event_loop_policy(None)
 
 
+def interrupt_self():
+    _thread.interrupt_main()
+
+
 class TestPolicy(asyncio.AbstractEventLoopPolicy):
 
     def __init__(self, loop_factory):
@@ -298,7 +304,7 @@ async def get_context():
 
             self.assertEqual({cvar: 2}, dict(runner.run(get_context())))
 
-    def test_recursine_run(self):
+    def test_recursive_run(self):
         async def g():
             pass
 
@@ -318,6 +324,57 @@ async def f():
                 ):
                     runner.run(f())
 
+    def test_interrupt_call_soon(self):
+        # The only case when task is not suspended by waiting a future
+        # or another task
+        assert threading.current_thread() is threading.main_thread()
+
+        async def coro():
+            with self.assertRaises(asyncio.CancelledError):
+                while True:
+                    await asyncio.sleep(0)
+            raise asyncio.CancelledError()
+
+        with asyncio.Runner() as runner:
+            runner.get_loop().call_later(0.1, interrupt_self)
+            with self.assertRaises(KeyboardInterrupt):
+                runner.run(coro())
+
+    def test_interrupt_wait(self):
+        # interrupting when waiting a future cancels both future and main task
+        assert threading.current_thread() is threading.main_thread()
+
+        async def coro(fut):
+            with self.assertRaises(asyncio.CancelledError):
+                await fut
+            raise asyncio.CancelledError()
+
+        with asyncio.Runner() as runner:
+            fut = runner.get_loop().create_future()
+            runner.get_loop().call_later(0.1, interrupt_self)
+
+            with self.assertRaises(KeyboardInterrupt):
+                runner.run(coro(fut))
+
+            self.assertTrue(fut.cancelled())
+
+    def test_interrupt_cancelled_task(self):
+        # interrupting cancelled main task doesn't raise KeyboardInterrupt
+        assert threading.current_thread() is threading.main_thread()
+
+        async def subtask(task):
+            await asyncio.sleep(0)
+            task.cancel()
+            interrupt_self()
+
+        async def coro():
+            asyncio.create_task(subtask(asyncio.current_task()))
+            await asyncio.sleep(10)
+
+        with asyncio.Runner() as runner:
+            with self.assertRaises(asyncio.CancelledError):
+                runner.run(coro())
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2022-03-25-01-27-25.bpo-39622.ieBIMp.rst b/Misc/NEWS.d/next/Library/2022-03-25-01-27-25.bpo-39622.ieBIMp.rst
new file mode 100644
index 000000000000000..25c6aa3703ad219
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-03-25-01-27-25.bpo-39622.ieBIMp.rst
@@ -0,0 +1 @@
+Handle Ctrl+C in asyncio programs to interrupt the main task.

From 2fd2ab760fef30b73999f19feee027c87e9f2d4e Mon Sep 17 00:00:00 2001
From: Andrew Svetlov <andrew.svetlov@gmail.com>
Date: Fri, 25 Mar 2022 01:49:49 +0200
Subject: [PATCH 02/20] Use SIGINT only to catch Ctrl+C

---
 Lib/asyncio/runners.py | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/Lib/asyncio/runners.py b/Lib/asyncio/runners.py
index 33a6e9825e2a2f3..d07c0386b7f38a5 100644
--- a/Lib/asyncio/runners.py
+++ b/Lib/asyncio/runners.py
@@ -97,10 +97,11 @@ def run(self, coro, *, context=None):
             context = self._context
         task = self._loop.create_task(coro, context=context)
 
-        if sys.platform == "win32":
-            signum = signal.SIGBREAK
-        else:
-            signum = signal.SIGINT
+        # if sys.platform == "win32":
+        #     signum = signal.SIGBREAK
+        # else:
+        #     signum = signal.SIGINT
+        signum = signal.SIGINT
         if (threading.current_thread() is threading.main_thread()
             and signal.getsignal(signum) is signal.default_int_handler
         ):

From fc2938c2b339ab7c987c45323fc431a8b3b8c43f Mon Sep 17 00:00:00 2001
From: Andrew Svetlov <andrew.svetlov@gmail.com>
Date: Fri, 25 Mar 2022 02:27:55 +0200
Subject: [PATCH 03/20] Cleanup

---
 Lib/asyncio/runners.py | 14 ++++----------
 1 file changed, 4 insertions(+), 10 deletions(-)

diff --git a/Lib/asyncio/runners.py b/Lib/asyncio/runners.py
index d07c0386b7f38a5..543275f009ef54c 100644
--- a/Lib/asyncio/runners.py
+++ b/Lib/asyncio/runners.py
@@ -4,7 +4,6 @@
 import enum
 import functools
 import threading
-import platform
 import signal
 import sys
 from . import coroutines
@@ -97,16 +96,11 @@ def run(self, coro, *, context=None):
             context = self._context
         task = self._loop.create_task(coro, context=context)
 
-        # if sys.platform == "win32":
-        #     signum = signal.SIGBREAK
-        # else:
-        #     signum = signal.SIGINT
-        signum = signal.SIGINT
         if (threading.current_thread() is threading.main_thread()
-            and signal.getsignal(signum) is signal.default_int_handler
+            and signal.getsignal(signal.SIGINT) is signal.default_int_handler
         ):
             sigint_handler = functools.partial(self._on_sigint, main_task=task)
-            signal.signal(signum, sigint_handler)
+            signal.signal(signal.SIGINT, sigint_handler)
         else:
             sigint_handler = None
 
@@ -120,9 +114,9 @@ def run(self, coro, *, context=None):
                 raise  # CancelledError
         finally:
             if (sigint_handler is not None
-                and signal.getsignal(signum) is sigint_handler
+                and signal.getsignal(signal.SIGINT) is sigint_handler
             ):
-                signal.signal(signum, signal.default_int_handler)
+                signal.signal(signal.SIGINT, signal.default_int_handler)
 
     def _lazy_init(self):
         if self._state is _State.CLOSED:

From fb78c7e81a6490e3e940b84917715acc3190b66c Mon Sep 17 00:00:00 2001
From: Andrew Svetlov <andrew.svetlov@gmail.com>
Date: Sat, 26 Mar 2022 00:42:27 +0200
Subject: [PATCH 04/20] Make interruption stablier

---
 Lib/asyncio/runners.py | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/Lib/asyncio/runners.py b/Lib/asyncio/runners.py
index 543275f009ef54c..60c41a2a34427b7 100644
--- a/Lib/asyncio/runners.py
+++ b/Lib/asyncio/runners.py
@@ -133,9 +133,13 @@ def _lazy_init(self):
         self._state = _State.INITIALIZED
 
     def _on_sigint(self, signum, frame, main_task):
+        if self._loop is None or self._loop.is_closing():
+            return
         self._interrunt_count += 1
-        if self._interrunt_count == 1:
+        if self._interrunt_count == 1 and not main_task.done():
             main_task.cancel()
+            # wakeup a loop if it is blocked by selector.select() with long timeour
+            self._loop.call_soon_threadsafe(lambda: None)
             return
         raise KeyboardInterrupt()
 

From 2fbe6907f6777417d0cbb03327407dca5ebce35c Mon Sep 17 00:00:00 2001
From: Andrew Svetlov <andrew.svetlov@gmail.com>
Date: Sat, 26 Mar 2022 14:20:03 +0200
Subject: [PATCH 05/20] Drop redundant checks

---
 Lib/asyncio/runners.py | 2 --
 1 file changed, 2 deletions(-)

diff --git a/Lib/asyncio/runners.py b/Lib/asyncio/runners.py
index 60c41a2a34427b7..f827ea6d345eee8 100644
--- a/Lib/asyncio/runners.py
+++ b/Lib/asyncio/runners.py
@@ -133,8 +133,6 @@ def _lazy_init(self):
         self._state = _State.INITIALIZED
 
     def _on_sigint(self, signum, frame, main_task):
-        if self._loop is None or self._loop.is_closing():
-            return
         self._interrunt_count += 1
         if self._interrunt_count == 1 and not main_task.done():
             main_task.cancel()

From 998ff9fda59a7b292e6736216103df648967b36e Mon Sep 17 00:00:00 2001
From: Andrew Svetlov <andrew.svetlov@gmail.com>
Date: Sat, 26 Mar 2022 16:05:45 +0200
Subject: [PATCH 06/20] Update Lib/asyncio/runners.py

Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
---
 Lib/asyncio/runners.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Lib/asyncio/runners.py b/Lib/asyncio/runners.py
index 616628fbfef863b..f473ceecfc53ff1 100644
--- a/Lib/asyncio/runners.py
+++ b/Lib/asyncio/runners.py
@@ -136,7 +136,7 @@ def _on_sigint(self, signum, frame, main_task):
         self._interrunt_count += 1
         if self._interrunt_count == 1 and not main_task.done():
             main_task.cancel()
-            # wakeup a loop if it is blocked by selector.select() with long timeour
+            # wakeup loop if it is blocked by select() with long timeout
             self._loop.call_soon_threadsafe(lambda: None)
             return
         raise KeyboardInterrupt()

From e526b96a038f1293a80b7cccfc80d5fb251886d6 Mon Sep 17 00:00:00 2001
From: Andrew Svetlov <andrew.svetlov@gmail.com>
Date: Tue, 29 Mar 2022 00:29:16 +0300
Subject: [PATCH 07/20] Add a docs section about keyboard interruption

---
 Doc/library/asyncio-runner.rst | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/Doc/library/asyncio-runner.rst b/Doc/library/asyncio-runner.rst
index 31becf192ada315..44a0c435a8702cc 100644
--- a/Doc/library/asyncio-runner.rst
+++ b/Doc/library/asyncio-runner.rst
@@ -119,3 +119,30 @@ Runner context manager
 
       Embedded *loop* and *context* are created at the :keyword:`with` body entering
       or the first call of :meth:`run` or :meth:`get_loop`.
+
+
+Handling Keyboard Interruption
+==============================
+
+.. versionadded:: 3.11
+
+When a user hits the interrupt key (normally :kbd:`Ctrl-C`), :exc:`KeyboardInterrupt`
+exception is raised in the main thread by default.
+
+This approach doesn't work with asyncio, the interruption should never break asyncio
+internals itself but stop a user code.
+
+Asyncio handles the keyboard interruption as follows:
+
+1. :meth:`asyncio.Runner.run` installs a custom :const:`signal.SIGINT` handler before
+   async function execution and reverts it back at the exit from the function.
+2. The :class:`~asyncio.Runner` creates the main task for passed async function
+   processing.
+3. When the interruption is requested, the custom signal handler cancels the main task
+   by :meth:`~asyncio.Task.cancel` call.  :exc:`asyncio.CancelledError` is raised
+   inside the the main task.  The exception unwinds, ``try/except`` and ``try/finally``
+   blocks can be used for resource cleanup.  After the main task is finished,
+   :meth:`asyncio.Runner.run` raises :exc:`KeyboardInterrupt`.
+4. A user could write a thight loop, :meth:`~asyncio.Task.cancel` cannot break it.
+   That's why the second and consequent :kbd:`Ctrl-C` hits immediatelly raise
+   :exc:`KeyboardInterrupt` to stop the program abnormally.

From 09cf9236ca384b04cde4962d4c783a219e9521ce Mon Sep 17 00:00:00 2001
From: Andrew Svetlov <andrew.svetlov@gmail.com>
Date: Tue, 29 Mar 2022 18:04:56 +0300
Subject: [PATCH 08/20] Update Doc/library/asyncio-runner.rst

Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
---
 Doc/library/asyncio-runner.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Doc/library/asyncio-runner.rst b/Doc/library/asyncio-runner.rst
index 44a0c435a8702cc..5f8fc4be3eaac28 100644
--- a/Doc/library/asyncio-runner.rst
+++ b/Doc/library/asyncio-runner.rst
@@ -135,7 +135,7 @@ internals itself but stop a user code.
 Asyncio handles the keyboard interruption as follows:
 
 1. :meth:`asyncio.Runner.run` installs a custom :const:`signal.SIGINT` handler before
-   async function execution and reverts it back at the exit from the function.
+   any user code is executed and removes it when exiting from the function.
 2. The :class:`~asyncio.Runner` creates the main task for passed async function
    processing.
 3. When the interruption is requested, the custom signal handler cancels the main task

From 65edc7e9848ab34892e77485b59a877c36f2fdff Mon Sep 17 00:00:00 2001
From: Andrew Svetlov <andrew.svetlov@gmail.com>
Date: Tue, 29 Mar 2022 18:05:09 +0300
Subject: [PATCH 09/20] Update Doc/library/asyncio-runner.rst

Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
---
 Doc/library/asyncio-runner.rst | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Doc/library/asyncio-runner.rst b/Doc/library/asyncio-runner.rst
index 5f8fc4be3eaac28..49452187d00ad11 100644
--- a/Doc/library/asyncio-runner.rst
+++ b/Doc/library/asyncio-runner.rst
@@ -136,8 +136,8 @@ Asyncio handles the keyboard interruption as follows:
 
 1. :meth:`asyncio.Runner.run` installs a custom :const:`signal.SIGINT` handler before
    any user code is executed and removes it when exiting from the function.
-2. The :class:`~asyncio.Runner` creates the main task for passed async function
-   processing.
+2. The :class:`~asyncio.Runner` creates the main task for the passed coroutine for 
+    its execution.
 3. When the interruption is requested, the custom signal handler cancels the main task
    by :meth:`~asyncio.Task.cancel` call.  :exc:`asyncio.CancelledError` is raised
    inside the the main task.  The exception unwinds, ``try/except`` and ``try/finally``

From 60c7390f80a667d3b102c9fedea2f9176f2153c9 Mon Sep 17 00:00:00 2001
From: Andrew Svetlov <andrew.svetlov@gmail.com>
Date: Tue, 29 Mar 2022 18:05:48 +0300
Subject: [PATCH 10/20] Update Doc/library/asyncio-runner.rst

Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
---
 Doc/library/asyncio-runner.rst | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/Doc/library/asyncio-runner.rst b/Doc/library/asyncio-runner.rst
index 49452187d00ad11..51009f92aaa26ec 100644
--- a/Doc/library/asyncio-runner.rst
+++ b/Doc/library/asyncio-runner.rst
@@ -138,11 +138,11 @@ Asyncio handles the keyboard interruption as follows:
    any user code is executed and removes it when exiting from the function.
 2. The :class:`~asyncio.Runner` creates the main task for the passed coroutine for 
     its execution.
-3. When the interruption is requested, the custom signal handler cancels the main task
-   by :meth:`~asyncio.Task.cancel` call.  :exc:`asyncio.CancelledError` is raised
-   inside the the main task.  The exception unwinds, ``try/except`` and ``try/finally``
-   blocks can be used for resource cleanup.  After the main task is finished,
-   :meth:`asyncio.Runner.run` raises :exc:`KeyboardInterrupt`.
+3. When :const:`signal.SIGINT` is raised by :kbd:`Ctrl-C`, the custom signal handler
+   cancels the main task by calling :meth:`asyncio.Task.cancel` which raises 
+   :exc:`asyncio.CancelledError` inside the the main task.  This causes the Python stack 
+   to unwind,  ``try/except`` and ``try/finally`` blocks can be used for resource cleanup. 
+   After the main task is cancelled, :meth:`asyncio.Runner.run` raises :exc:`KeyboardInterrupt`.
 4. A user could write a thight loop, :meth:`~asyncio.Task.cancel` cannot break it.
    That's why the second and consequent :kbd:`Ctrl-C` hits immediatelly raise
    :exc:`KeyboardInterrupt` to stop the program abnormally.

From 6fa8016083c560f6f226868e163693842d3e1d7a Mon Sep 17 00:00:00 2001
From: Andrew Svetlov <andrew.svetlov@gmail.com>
Date: Tue, 29 Mar 2022 18:06:36 +0300
Subject: [PATCH 11/20] Update Doc/library/asyncio-runner.rst

Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
---
 Doc/library/asyncio-runner.rst | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/Doc/library/asyncio-runner.rst b/Doc/library/asyncio-runner.rst
index 51009f92aaa26ec..14530070f6f2b80 100644
--- a/Doc/library/asyncio-runner.rst
+++ b/Doc/library/asyncio-runner.rst
@@ -143,6 +143,6 @@ Asyncio handles the keyboard interruption as follows:
    :exc:`asyncio.CancelledError` inside the the main task.  This causes the Python stack 
    to unwind,  ``try/except`` and ``try/finally`` blocks can be used for resource cleanup. 
    After the main task is cancelled, :meth:`asyncio.Runner.run` raises :exc:`KeyboardInterrupt`.
-4. A user could write a thight loop, :meth:`~asyncio.Task.cancel` cannot break it.
-   That's why the second and consequent :kbd:`Ctrl-C` hits immediatelly raise
-   :exc:`KeyboardInterrupt` to stop the program abnormally.
+4. A user could write a tight loop which cannot be interrupted by :meth:`asyncio.Task.cancel`,
+   in which case the second following :kbd:`Ctrl-C` immediately raises the :exc:`KeyboardInterrupt`
+   without cancelling the main task.

From d456248a8c4630b1faacaa270596a994fb9bbe03 Mon Sep 17 00:00:00 2001
From: Andrew Svetlov <andrew.svetlov@gmail.com>
Date: Tue, 29 Mar 2022 18:06:54 +0300
Subject: [PATCH 12/20] Update Doc/library/asyncio-runner.rst

Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
---
 Doc/library/asyncio-runner.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Doc/library/asyncio-runner.rst b/Doc/library/asyncio-runner.rst
index 14530070f6f2b80..4540eb5b741cda7 100644
--- a/Doc/library/asyncio-runner.rst
+++ b/Doc/library/asyncio-runner.rst
@@ -126,7 +126,7 @@ Handling Keyboard Interruption
 
 .. versionadded:: 3.11
 
-When a user hits the interrupt key (normally :kbd:`Ctrl-C`), :exc:`KeyboardInterrupt`
+When :const:`signal.SIGINT` is raised by :kbd:`Ctrl-C`, :exc:`KeyboardInterrupt`
 exception is raised in the main thread by default.
 
 This approach doesn't work with asyncio, the interruption should never break asyncio

From 3d6ce845ca9a9a8009fc394c4869e5c2d37c7ac2 Mon Sep 17 00:00:00 2001
From: Andrew Svetlov <andrew.svetlov@gmail.com>
Date: Tue, 29 Mar 2022 18:07:19 +0300
Subject: [PATCH 13/20] Update Doc/library/asyncio-runner.rst

Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
---
 Doc/library/asyncio-runner.rst | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/Doc/library/asyncio-runner.rst b/Doc/library/asyncio-runner.rst
index 4540eb5b741cda7..43abfe77c807629 100644
--- a/Doc/library/asyncio-runner.rst
+++ b/Doc/library/asyncio-runner.rst
@@ -127,7 +127,10 @@ Handling Keyboard Interruption
 .. versionadded:: 3.11
 
 When :const:`signal.SIGINT` is raised by :kbd:`Ctrl-C`, :exc:`KeyboardInterrupt`
-exception is raised in the main thread by default.
+exception is raised in the main thread by default. However this doesn't work with :mod:`asyncio` 
+because it can interrupt asyncio internals and can hang the program from exiting.
+
+To mitigate this issue, :mod:`asyncio` handles :const:`signal.SIGINT` as follows:
 
 This approach doesn't work with asyncio, the interruption should never break asyncio
 internals itself but stop a user code.

From 881c5eff2a233f67f2f4ec749c0e01a49a8622bc Mon Sep 17 00:00:00 2001
From: Andrew Svetlov <andrew.svetlov@gmail.com>
Date: Tue, 29 Mar 2022 18:07:30 +0300
Subject: [PATCH 14/20] Update Doc/library/asyncio-runner.rst

Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
---
 Doc/library/asyncio-runner.rst | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/Doc/library/asyncio-runner.rst b/Doc/library/asyncio-runner.rst
index 43abfe77c807629..b3842b45df7d506 100644
--- a/Doc/library/asyncio-runner.rst
+++ b/Doc/library/asyncio-runner.rst
@@ -132,10 +132,6 @@ because it can interrupt asyncio internals and can hang the program from exiting
 
 To mitigate this issue, :mod:`asyncio` handles :const:`signal.SIGINT` as follows:
 
-This approach doesn't work with asyncio, the interruption should never break asyncio
-internals itself but stop a user code.
-
-Asyncio handles the keyboard interruption as follows:
 
 1. :meth:`asyncio.Runner.run` installs a custom :const:`signal.SIGINT` handler before
    any user code is executed and removes it when exiting from the function.

From ca9bccc91e776ee15d4c8a8e3a15eaa3b1ac1518 Mon Sep 17 00:00:00 2001
From: Andrew Svetlov <andrew.svetlov@gmail.com>
Date: Tue, 29 Mar 2022 18:07:45 +0300
Subject: [PATCH 15/20] Update Lib/asyncio/runners.py

Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
---
 Lib/asyncio/runners.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Lib/asyncio/runners.py b/Lib/asyncio/runners.py
index f473ceecfc53ff1..6e2ab2edab93232 100644
--- a/Lib/asyncio/runners.py
+++ b/Lib/asyncio/runners.py
@@ -52,7 +52,7 @@ def __init__(self, *, debug=None, loop_factory=None):
         self._loop_factory = loop_factory
         self._loop = None
         self._context = None
-        self._interrunt_count = 0
+        self._interrupt_count = 0
 
     def __enter__(self):
         self._lazy_init()

From 4202b4add18a10e0dbc40a8f811977c14565d562 Mon Sep 17 00:00:00 2001
From: Andrew Svetlov <andrew.svetlov@gmail.com>
Date: Tue, 29 Mar 2022 18:07:51 +0300
Subject: [PATCH 16/20] Update Lib/asyncio/runners.py

Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
---
 Lib/asyncio/runners.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Lib/asyncio/runners.py b/Lib/asyncio/runners.py
index 6e2ab2edab93232..1ab8833407cdb3b 100644
--- a/Lib/asyncio/runners.py
+++ b/Lib/asyncio/runners.py
@@ -108,7 +108,7 @@ def run(self, coro, *, context=None):
         try:
             return self._loop.run_until_complete(task)
         except exceptions.CancelledError:
-            if self._interrunt_count > 0 and task.uncancel() == 0:
+            if self._interrupt_count > 0 and task.uncancel() == 0:
                 raise KeyboardInterrupt()
             else:
                 raise  # CancelledError

From 9e6cd475ab8d1714ba3e13e4b90aca068dea3e9b Mon Sep 17 00:00:00 2001
From: Andrew Svetlov <andrew.svetlov@gmail.com>
Date: Tue, 29 Mar 2022 18:07:58 +0300
Subject: [PATCH 17/20] Update Lib/asyncio/runners.py

Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
---
 Lib/asyncio/runners.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Lib/asyncio/runners.py b/Lib/asyncio/runners.py
index 1ab8833407cdb3b..557c88d790243fc 100644
--- a/Lib/asyncio/runners.py
+++ b/Lib/asyncio/runners.py
@@ -133,7 +133,7 @@ def _lazy_init(self):
         self._state = _State.INITIALIZED
 
     def _on_sigint(self, signum, frame, main_task):
-        self._interrunt_count += 1
+        self._interrupt_count += 1
         if self._interrunt_count == 1 and not main_task.done():
             main_task.cancel()
             # wakeup loop if it is blocked by select() with long timeout

From 0c8a79aa8f6785ffe4463990d85daeb99be5edad Mon Sep 17 00:00:00 2001
From: Andrew Svetlov <andrew.svetlov@gmail.com>
Date: Tue, 29 Mar 2022 18:08:08 +0300
Subject: [PATCH 18/20] Update Lib/asyncio/runners.py

Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
---
 Lib/asyncio/runners.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Lib/asyncio/runners.py b/Lib/asyncio/runners.py
index 557c88d790243fc..96b115bf95ab241 100644
--- a/Lib/asyncio/runners.py
+++ b/Lib/asyncio/runners.py
@@ -134,7 +134,7 @@ def _lazy_init(self):
 
     def _on_sigint(self, signum, frame, main_task):
         self._interrupt_count += 1
-        if self._interrunt_count == 1 and not main_task.done():
+        if self._interrupt_count == 1 and not main_task.done():
             main_task.cancel()
             # wakeup loop if it is blocked by select() with long timeout
             self._loop.call_soon_threadsafe(lambda: None)

From 2a70c20309ff647c000002442846012e935adb0c Mon Sep 17 00:00:00 2001
From: Andrew Svetlov <andrew.svetlov@gmail.com>
Date: Tue, 29 Mar 2022 18:08:18 +0300
Subject: [PATCH 19/20] Update Lib/asyncio/runners.py

Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
---
 Lib/asyncio/runners.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Lib/asyncio/runners.py b/Lib/asyncio/runners.py
index 96b115bf95ab241..2bb9ca331fd6838 100644
--- a/Lib/asyncio/runners.py
+++ b/Lib/asyncio/runners.py
@@ -104,7 +104,7 @@ def run(self, coro, *, context=None):
         else:
             sigint_handler = None
 
-        self._interrunt_count = 0
+        self._interrupt_count = 0
         try:
             return self._loop.run_until_complete(task)
         except exceptions.CancelledError:

From 5c8a6fa4bacb8cb9b50e911b1494e58d40ce9357 Mon Sep 17 00:00:00 2001
From: Andrew Svetlov <andrew.svetlov@gmail.com>
Date: Tue, 29 Mar 2022 20:49:34 +0300
Subject: [PATCH 20/20] Fix patchcheck

---
 Doc/library/asyncio-runner.rst | 25 +++++++++++++------------
 1 file changed, 13 insertions(+), 12 deletions(-)

diff --git a/Doc/library/asyncio-runner.rst b/Doc/library/asyncio-runner.rst
index b3842b45df7d506..a526b459f74748c 100644
--- a/Doc/library/asyncio-runner.rst
+++ b/Doc/library/asyncio-runner.rst
@@ -127,21 +127,22 @@ Handling Keyboard Interruption
 .. versionadded:: 3.11
 
 When :const:`signal.SIGINT` is raised by :kbd:`Ctrl-C`, :exc:`KeyboardInterrupt`
-exception is raised in the main thread by default. However this doesn't work with :mod:`asyncio` 
-because it can interrupt asyncio internals and can hang the program from exiting.
+exception is raised in the main thread by default. However this doesn't work with
+:mod:`asyncio` because it can interrupt asyncio internals and can hang the program from
+exiting.
 
 To mitigate this issue, :mod:`asyncio` handles :const:`signal.SIGINT` as follows:
 
-
 1. :meth:`asyncio.Runner.run` installs a custom :const:`signal.SIGINT` handler before
    any user code is executed and removes it when exiting from the function.
-2. The :class:`~asyncio.Runner` creates the main task for the passed coroutine for 
-    its execution.
+2. The :class:`~asyncio.Runner` creates the main task for the passed coroutine for its
+   execution.
 3. When :const:`signal.SIGINT` is raised by :kbd:`Ctrl-C`, the custom signal handler
-   cancels the main task by calling :meth:`asyncio.Task.cancel` which raises 
-   :exc:`asyncio.CancelledError` inside the the main task.  This causes the Python stack 
-   to unwind,  ``try/except`` and ``try/finally`` blocks can be used for resource cleanup. 
-   After the main task is cancelled, :meth:`asyncio.Runner.run` raises :exc:`KeyboardInterrupt`.
-4. A user could write a tight loop which cannot be interrupted by :meth:`asyncio.Task.cancel`,
-   in which case the second following :kbd:`Ctrl-C` immediately raises the :exc:`KeyboardInterrupt`
-   without cancelling the main task.
+   cancels the main task by calling :meth:`asyncio.Task.cancel` which raises
+   :exc:`asyncio.CancelledError` inside the the main task.  This causes the Python stack
+   to unwind, ``try/except`` and ``try/finally`` blocks can be used for resource
+   cleanup.  After the main task is cancelled, :meth:`asyncio.Runner.run` raises
+   :exc:`KeyboardInterrupt`.
+4. A user could write a tight loop which cannot be interrupted by
+   :meth:`asyncio.Task.cancel`, in which case the second following :kbd:`Ctrl-C`
+   immediately raises the :exc:`KeyboardInterrupt` without cancelling the main task.
