Skip to content

Commit 3089438

Browse files
Kernel subshells (JEP91) implementation (#1249)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 0fa5439 commit 3089438

16 files changed

+988
-68
lines changed

docs/api/ipykernel.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,30 @@ Submodules
110110
:show-inheritance:
111111

112112

113+
.. automodule:: ipykernel.shellchannel
114+
:members:
115+
:undoc-members:
116+
:show-inheritance:
117+
118+
119+
.. automodule:: ipykernel.subshell
120+
:members:
121+
:undoc-members:
122+
:show-inheritance:
123+
124+
125+
.. automodule:: ipykernel.subshell_manager
126+
:members:
127+
:undoc-members:
128+
:show-inheritance:
129+
130+
131+
.. automodule:: ipykernel.thread
132+
:members:
133+
:undoc-members:
134+
:show-inheritance:
135+
136+
113137
.. automodule:: ipykernel.trio_runner
114138
:members:
115139
:undoc-members:

ipykernel/control.py

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,11 @@
11
"""A thread for a control channel."""
2-
from threading import Event, Thread
32

4-
from anyio import create_task_group, run, to_thread
3+
from .thread import CONTROL_THREAD_NAME, BaseThread
54

6-
CONTROL_THREAD_NAME = "Control"
75

8-
9-
class ControlThread(Thread):
6+
class ControlThread(BaseThread):
107
"""A thread for a control channel."""
118

129
def __init__(self, **kwargs):
1310
"""Initialize the thread."""
14-
Thread.__init__(self, name=CONTROL_THREAD_NAME, **kwargs)
15-
self.pydev_do_not_trace = True
16-
self.is_pydev_daemon_thread = True
17-
self.__stop = Event()
18-
self._task = None
19-
20-
def set_task(self, task):
21-
self._task = task
22-
23-
def run(self):
24-
"""Run the thread."""
25-
self.name = CONTROL_THREAD_NAME
26-
run(self._main)
27-
28-
async def _main(self):
29-
async with create_task_group() as tg:
30-
if self._task is not None:
31-
tg.start_soon(self._task)
32-
await to_thread.run_sync(self.__stop.wait)
33-
tg.cancel_scope.cancel()
34-
35-
def stop(self):
36-
"""Stop the thread.
37-
38-
This method is threadsafe.
39-
"""
40-
self.__stop.set()
11+
super().__init__(name=CONTROL_THREAD_NAME, **kwargs)

ipykernel/heartbeat.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def __init__(self, context, addr=None):
3232
"""Initialize the heartbeat thread."""
3333
if addr is None:
3434
addr = ("tcp", localhost(), 0)
35-
Thread.__init__(self, name="Heartbeat")
35+
super().__init__(name="Heartbeat")
3636
self.context = context
3737
self.transport, self.ip, self.port = addr
3838
self.original_port = self.port

ipykernel/iostream.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class _IOPubThread(Thread):
4040

4141
def __init__(self, tasks, **kwargs):
4242
"""Initialize the thread."""
43-
Thread.__init__(self, name="IOPub", **kwargs)
43+
super().__init__(name="IOPub", **kwargs)
4444
self._tasks = tasks
4545
self.pydev_do_not_trace = True
4646
self.is_pydev_daemon_thread = True
@@ -170,10 +170,10 @@ async def _handle_event(self):
170170
for _ in range(n_events):
171171
event_f = self._events.popleft()
172172
event_f()
173-
except Exception as e:
173+
except Exception:
174174
if self.thread.__stop.is_set():
175175
return
176-
raise e
176+
raise
177177

178178
def _setup_pipe_in(self):
179179
"""setup listening pipe for IOPub from forked subprocesses"""
@@ -202,10 +202,10 @@ async def _handle_pipe_msgs(self):
202202
try:
203203
while True:
204204
await self._handle_pipe_msg()
205-
except Exception as e:
205+
except Exception:
206206
if self.thread.__stop.is_set():
207207
return
208-
raise e
208+
raise
209209

210210
async def _handle_pipe_msg(self, msg=None):
211211
"""handle a pipe message from a subprocess"""

ipykernel/kernelapp.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
from .iostream import IOPubThread
5454
from .ipkernel import IPythonKernel
5555
from .parentpoller import ParentPollerUnix, ParentPollerWindows
56+
from .shellchannel import ShellChannelThread
5657
from .zmqshell import ZMQInteractiveShell
5758

5859
# -----------------------------------------------------------------------------
@@ -143,6 +144,7 @@ class IPKernelApp(BaseIPythonApplication, InteractiveShellApp, ConnectionFileMix
143144
iopub_socket = Any()
144145
iopub_thread = Any()
145146
control_thread = Any()
147+
shell_channel_thread = Any()
146148

147149
_ports = Dict()
148150

@@ -367,6 +369,7 @@ def init_control(self, context):
367369
self.control_socket.router_handover = 1
368370

369371
self.control_thread = ControlThread(daemon=True)
372+
self.shell_channel_thread = ShellChannelThread(context, self.shell_socket, daemon=True)
370373

371374
def init_iopub(self, context):
372375
"""Initialize the iopub channel."""
@@ -406,6 +409,10 @@ def close(self):
406409
self.log.debug("Closing control thread")
407410
self.control_thread.stop()
408411
self.control_thread.join()
412+
if self.shell_channel_thread and self.shell_channel_thread.is_alive():
413+
self.log.debug("Closing shell channel thread")
414+
self.shell_channel_thread.stop()
415+
self.shell_channel_thread.join()
409416

410417
if self.debugpy_socket and not self.debugpy_socket.closed:
411418
self.debugpy_socket.close()
@@ -562,6 +569,7 @@ def init_kernel(self):
562569
debug_shell_socket=self.debug_shell_socket,
563570
shell_socket=self.shell_socket,
564571
control_thread=self.control_thread,
572+
shell_channel_thread=self.shell_channel_thread,
565573
iopub_thread=self.iopub_thread,
566574
iopub_socket=self.iopub_socket,
567575
stdin_socket=self.stdin_socket,

0 commit comments

Comments
 (0)