Skip to content

Commit 05e3876

Browse files
committed
Use UnknownSubshellError for mising subshell
1 parent c30324d commit 05e3876

3 files changed

Lines changed: 48 additions & 20 deletions

File tree

‎ipykernel/kernelbase.py‎

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959

6060
from ._version import kernel_protocol_version
6161
from .iostream import OutStream
62+
from .subshell_manager import UnknownSubshellError
6263
from .utils import LazyDict, _async_in_context
6364

6465
psutil: t.Any | None = None
@@ -603,8 +604,8 @@ async def shell_channel_thread_main(self, msg):
603604
subshell_manager = self.shell_channel_thread.manager
604605
try:
605606
socket = subshell_manager.get_shell_channel_to_subshell_socket(subshell_id)
606-
except KeyError:
607-
self._send_unknown_subshell_reply(idents, msg3, subshell_id)
607+
except UnknownSubshellError as err:
608+
self._send_unknown_subshell_reply(idents, msg3, err)
608609
return
609610
assert socket is not None
610611
socket.send_multipart(msg, copy=False)
@@ -1382,7 +1383,7 @@ def _send_abort_reply(self, stream, msg, idents):
13821383
ident=idents,
13831384
)
13841385

1385-
def _send_unknown_subshell_reply(self, idents, msg, subshell_id) -> None:
1386+
def _send_unknown_subshell_reply(self, idents, msg, err: UnknownSubshellError) -> None:
13861387
"""Send an error reply to a request addressed to a subshell that is not there.
13871388
13881389
Runs in the shell channel thread, so it writes to the shell socket
@@ -1396,17 +1397,12 @@ def _send_unknown_subshell_reply(self, idents, msg, subshell_id) -> None:
13961397
if not self.session:
13971398
return
13981399
msg_type = msg["header"]["msg_type"]
1399-
self.log.warning(
1400-
"Cannot handle %s %s: unknown subshell_id %r",
1401-
msg_type,
1402-
msg["header"]["msg_id"],
1403-
subshell_id,
1404-
)
1400+
self.log.warning("Cannot handle %s %s: %s", msg_type, msg["header"]["msg_id"], err)
14051401
self._publish_status("busy", "shell", parent=msg)
14061402
content = {
14071403
"status": "error",
1408-
"ename": "KeyError",
1409-
"evalue": f"Unknown subshell_id {subshell_id!r}",
1404+
"ename": type(err).__name__,
1405+
"evalue": str(err),
14101406
"traceback": [],
14111407
}
14121408
md = self.init_metadata(msg)

‎ipykernel/subshell_manager.py‎

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,20 @@
1919
from .utils import _async_in_context
2020

2121

22+
class UnknownSubshellError(KeyError):
23+
"""A subshell_id that does not name an existing subshell.
24+
25+
.. versionadded:: 7.4
26+
"""
27+
28+
def __init__(self, subshell_id: str) -> None:
29+
super().__init__(subshell_id)
30+
self.subshell_id = subshell_id
31+
32+
def __str__(self) -> str:
33+
return f"Unknown subshell_id {self.subshell_id!r}"
34+
35+
2236
class SubshellManager:
2337
"""A manager of subshells.
2438
@@ -87,13 +101,24 @@ def close(self) -> None:
87101
self._main_to_shell_channel.close()
88102
self._shell_channel_to_main.close()
89103

104+
def _get_subshell(self, subshell_id: str) -> SubshellThread:
105+
"""Return the thread of the specified subshell.
106+
107+
The caller must hold ``_lock_cache``. Raises ``UnknownSubshellError`` if
108+
there is no such subshell.
109+
"""
110+
try:
111+
return self._cache[subshell_id]
112+
except KeyError:
113+
raise UnknownSubshellError(subshell_id) from None
114+
90115
def get_shell_channel_to_subshell_pair(self, subshell_id: str | None) -> SocketPair:
91116
"""Return the inproc socket pair used to send messages from the shell channel
92117
to a particular subshell or main shell."""
93118
if subshell_id is None:
94119
return self._shell_channel_to_main
95120
with self._lock_cache:
96-
return self._cache[subshell_id].shell_channel_to_subshell
121+
return self._get_subshell(subshell_id).shell_channel_to_subshell
97122

98123
def get_subshell_to_shell_channel_socket(self, subshell_id: str | None) -> zmq.Socket[t.Any]:
99124
"""Return the socket used by a particular subshell or main shell to send
@@ -102,7 +127,7 @@ def get_subshell_to_shell_channel_socket(self, subshell_id: str | None) -> zmq.S
102127
if subshell_id is None:
103128
return self._main_to_shell_channel.from_socket
104129
with self._lock_cache:
105-
return self._cache[subshell_id].subshell_to_shell_channel.from_socket
130+
return self._get_subshell(subshell_id).subshell_to_shell_channel.from_socket
106131

107132
def get_shell_channel_to_subshell_socket(self, subshell_id: str | None) -> zmq.Socket[t.Any]:
108133
"""Return the socket used by the shell channel to send messages to a particular
@@ -113,12 +138,12 @@ def get_shell_channel_to_subshell_socket(self, subshell_id: str | None) -> zmq.S
113138
def get_subshell_aborting(self, subshell_id: str) -> bool:
114139
"""Get the boolean aborting flag of the specified subshell."""
115140
with self._lock_cache:
116-
return self._cache[subshell_id].aborting
141+
return self._get_subshell(subshell_id).aborting
117142

118143
def get_subshell_asyncio_lock(self, subshell_id: str) -> asyncio.Lock:
119144
"""Return the asyncio lock belonging to the specified subshell."""
120145
with self._lock_cache:
121-
return self._cache[subshell_id].asyncio_lock
146+
return self._get_subshell(subshell_id).asyncio_lock
122147

123148
def list_subshell(self) -> list[str]:
124149
"""Return list of current subshell ids.
@@ -141,7 +166,7 @@ def set_on_recv_callback(self, on_recv_callback):
141166
def set_subshell_aborting(self, subshell_id: str, aborting: bool) -> None:
142167
"""Set the aborting flag of the specified subshell."""
143168
with self._lock_cache:
144-
self._cache[subshell_id].aborting = aborting
169+
self._get_subshell(subshell_id).aborting = aborting
145170

146171
def subshell_id_from_thread_id(self, thread_id: int) -> str | None:
147172
"""Return subshell_id of the specified thread_id.
@@ -185,14 +210,15 @@ def _create_subshell(self) -> str:
185210
def _delete_subshell(self, subshell_id: str) -> None:
186211
"""Delete subshell identified by subshell_id.
187212
188-
Raises key error if subshell_id not in cache.
213+
Raises ``UnknownSubshellError`` if subshell_id not in cache.
189214
"""
190215
assert current_thread().name == SHELL_CHANNEL_THREAD_NAME
191216

192217
with self._lock_cache:
193-
subshell_threwad = self._cache.pop(subshell_id)
218+
subshell_thread = self._get_subshell(subshell_id)
219+
del self._cache[subshell_id]
194220

195-
self._stop_subshell(subshell_threwad)
221+
self._stop_subshell(subshell_thread)
196222

197223
def _process_control_request(
198224
self,

‎tests/test_subshells.py‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,14 +399,20 @@ def test_unknown_subshell_id():
399399
with new_kernel() as kc:
400400
subshell_id = create_subshell_helper(kc)["subshell_id"]
401401
delete_subshell_helper(kc, subshell_id)
402+
403+
# Deleting it again names the missing subshell in the same way.
404+
content = delete_subshell_helper(kc, subshell_id)
405+
assert content["status"] == "error"
406+
assert content["evalue"] == f"Unknown subshell_id {subshell_id!r}"
402407
flush_channels(kc)
403408

404409
msg = execute_request(kc, "a = 1", subshell_id)
405410
msg_id = msg["header"]["msg_id"]
406411

407412
reply = get_reply(kc, msg_id, TIMEOUT)
408413
assert reply["content"]["status"] == "error"
409-
assert subshell_id in reply["content"]["evalue"]
414+
assert reply["content"]["ename"] == "UnknownSubshellError"
415+
assert reply["content"]["evalue"] == f"Unknown subshell_id {subshell_id!r}"
410416

411417
states = []
412418
while True:

0 commit comments

Comments
 (0)