@@ -594,14 +594,18 @@ async def shell_channel_thread_main(self, msg):
594594
595595 # deserialize only the header to get subshell_id
596596 # Keep original message to send to subshell_id unmodified.
597- _ , msg2 = self .session .feed_identities (msg , copy = False )
597+ idents , msg2 = self .session .feed_identities (msg , copy = False )
598598 try :
599599 msg3 = self .session .deserialize (msg2 , content = False , copy = False )
600600 subshell_id = msg3 ["header" ].get ("subshell_id" )
601601
602602 # Find inproc pair socket to use to send message to correct subshell.
603603 subshell_manager = self .shell_channel_thread .manager
604- socket = subshell_manager .get_shell_channel_to_subshell_socket (subshell_id )
604+ try :
605+ socket = subshell_manager .get_shell_channel_to_subshell_socket (subshell_id )
606+ except KeyError :
607+ self ._send_unknown_subshell_reply (idents , msg3 , subshell_id )
608+ return
605609 assert socket is not None
606610 socket .send_multipart (msg , copy = False )
607611 except Exception :
@@ -1378,6 +1382,46 @@ def _send_abort_reply(self, stream, msg, idents):
13781382 ident = idents ,
13791383 )
13801384
1385+ def _send_unknown_subshell_reply (self , idents , msg , subshell_id ) -> None :
1386+ """Send an error reply to a request addressed to a subshell that is not there.
1387+
1388+ Runs in the shell channel thread, so it writes to the shell socket
1389+ directly instead of going through a subshell.
1390+
1391+ The busy and idle status messages matter as much as the reply here.
1392+ A client tracks the completion of a request by the idle status that
1393+ carries it as parent, and for message types that have no reply, such as
1394+ the comm messages, that status is all it has to go on.
1395+ """
1396+ if not self .session :
1397+ return
1398+ 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+ )
1405+ self ._publish_status ("busy" , "shell" , parent = msg )
1406+ content = {
1407+ "status" : "error" ,
1408+ "ename" : "KeyError" ,
1409+ "evalue" : f"Unknown subshell_id { subshell_id !r} " ,
1410+ "traceback" : [],
1411+ }
1412+ md = self .init_metadata (msg )
1413+ md = self .finish_metadata (msg , md , content )
1414+ md .update ({"status" : "error" })
1415+ self .session .send (
1416+ self .shell_stream ,
1417+ msg_type .rsplit ("_" , 1 )[0 ] + "_reply" ,
1418+ metadata = md ,
1419+ content = content ,
1420+ parent = msg ,
1421+ ident = idents ,
1422+ )
1423+ self ._publish_status ("idle" , "shell" , parent = msg )
1424+
13811425 def _no_raw_input (self ):
13821426 """Raise StdinNotImplementedError if active frontend doesn't support
13831427 stdin."""
0 commit comments