Skip to content

Commit 3638154

Browse files
authored
Merge pull request #1 from BoykoNeov/pr-1469-into-patch2
Run service-thread event loops on a selector loop on Windows (fix debugger deadlock under Proactor)
2 parents 6a75d0f + 02c895e commit 3638154

2 files changed

Lines changed: 28 additions & 3 deletions

File tree

ipykernel/iostream.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@
2222

2323
import zmq
2424
from jupyter_client.session import extract_header
25-
from tornado.ioloop import IOLoop
2625
from zmq.eventloop.zmqstream import ZMQStream
2726

27+
from .thread import make_selector_io_loop
28+
2829
# -----------------------------------------------------------------------------
2930
# Globals
3031
# -----------------------------------------------------------------------------
@@ -67,7 +68,7 @@ def __init__(self, socket, pipe=False, session=False):
6768
self.background_socket = BackgroundSocket(self)
6869
self._master_pid = os.getpid()
6970
self._pipe_flag = pipe
70-
self.io_loop = IOLoop(make_current=False)
71+
self.io_loop = make_selector_io_loop()
7172
if pipe:
7273
self._setup_pipe_in()
7374
self._local = threading.local()

ipykernel/thread.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Base class for threads."""
22

3+
import asyncio
4+
import sys
35
from threading import Thread
46

57
from tornado.ioloop import IOLoop
@@ -8,13 +10,35 @@
810
SHELL_CHANNEL_THREAD_NAME = "Shell channel"
911

1012

13+
def make_selector_io_loop() -> IOLoop:
14+
"""Create a non-current tornado ``IOLoop`` for an ipykernel service thread.
15+
16+
ipykernel runs its service channels -- control, IOPub, the shell channel and
17+
subshells -- on dedicated event loops in background threads. The process-wide
18+
asyncio loop on Windows is a ``ProactorEventLoop`` (so the main user-code loop
19+
can spawn asyncio subprocesses, see #1468/#1469), and Proactor has no native
20+
``add_reader``. Tornado therefore drives a Proactor loop's zmq sockets through
21+
a helper "Tornado selector" thread. When a debugger suspends every thread at a
22+
breakpoint on Python >= 3.12 (``sys.monitoring``), that un-exempt helper thread
23+
freezes mid-wake and deadlocks the control/debug read path (#1469).
24+
25+
These service loops never need Proactor's subprocess support, so we keep them
26+
on a ``SelectorEventLoop``: it implements ``add_reader`` natively and needs no
27+
helper thread. Only the main/user-code loop stays on Proactor. On non-Windows
28+
platforms the default loop is already selector-based, so this is a no-op there.
29+
"""
30+
if sys.platform == "win32":
31+
return IOLoop(make_current=False, asyncio_loop=asyncio.SelectorEventLoop())
32+
return IOLoop(make_current=False)
33+
34+
1135
class BaseThread(Thread):
1236
"""Base class for threads."""
1337

1438
def __init__(self, **kwargs):
1539
"""Initialize the thread."""
1640
super().__init__(**kwargs)
17-
self.io_loop = IOLoop(make_current=False)
41+
self.io_loop = make_selector_io_loop()
1842
self.pydev_do_not_trace = True
1943
self.is_pydev_daemon_thread = True
2044

0 commit comments

Comments
 (0)