@@ -205,6 +205,9 @@ def _default_ident(self):
205
205
# see https://github.com/jupyterlab/jupyterlab/issues/17785
206
206
_parent_ident : Mapping [str , bytes ]
207
207
208
+ # Asyncio lock for main shell thread.
209
+ _main_asyncio_lock : asyncio .Lock
210
+
208
211
@property
209
212
def _parent_header (self ):
210
213
warnings .warn (
@@ -327,6 +330,8 @@ def __init__(self, **kwargs):
327
330
}
328
331
)
329
332
333
+ self ._main_asyncio_lock = asyncio .Lock ()
334
+
330
335
async def dispatch_control (self , msg ):
331
336
"""Dispatch a control request, ensuring only one message is processed at a time."""
332
337
# Ensure only one control message is processed at a time
@@ -539,7 +544,7 @@ async def do_one_iteration(self):
539
544
This is now a coroutine
540
545
"""
541
546
# flush messages off of shell stream into the message queue
542
- if self .shell_stream :
547
+ if self .shell_stream and not self . _supports_kernel_subshells :
543
548
self .shell_stream .flush ()
544
549
# process at most one shell message per iteration
545
550
await self .process_one (wait = False )
@@ -649,56 +654,51 @@ async def shell_channel_thread_main(self, msg):
649
654
"""Handler for shell messages received on shell_channel_thread"""
650
655
assert threading .current_thread () == self .shell_channel_thread
651
656
652
- if self .session is None :
653
- return
654
-
655
- # deserialize only the header to get subshell_id
656
- # Keep original message to send to subshell_id unmodified.
657
- _ , msg2 = self .session .feed_identities (msg , copy = False )
658
- try :
659
- msg3 = self .session .deserialize (msg2 , content = False , copy = False )
660
- subshell_id = msg3 ["header" ].get ("subshell_id" )
661
-
662
- # Find inproc pair socket to use to send message to correct subshell.
663
- subshell_manager = self .shell_channel_thread .manager
664
- socket = subshell_manager .get_shell_channel_to_subshell_socket (subshell_id )
665
- assert socket is not None
666
- socket .send_multipart (msg , copy = False )
667
- except Exception :
668
- self .log .error ("Invalid message" , exc_info = True ) # noqa: G201
657
+ async with self .shell_channel_thread .asyncio_lock :
658
+ if self .session is None :
659
+ return
669
660
670
- if self .shell_stream :
671
- self .shell_stream .flush ()
661
+ # deserialize only the header to get subshell_id
662
+ # Keep original message to send to subshell_id unmodified.
663
+ _ , msg2 = self .session .feed_identities (msg , copy = False )
664
+ try :
665
+ msg3 = self .session .deserialize (msg2 , content = False , copy = False )
666
+ subshell_id = msg3 ["header" ].get ("subshell_id" )
667
+
668
+ # Find inproc pair socket to use to send message to correct subshell.
669
+ subshell_manager = self .shell_channel_thread .manager
670
+ socket = subshell_manager .get_shell_channel_to_subshell_socket (subshell_id )
671
+ assert socket is not None
672
+ socket .send_multipart (msg , copy = False )
673
+ except Exception :
674
+ self .log .error ("Invalid message" , exc_info = True ) # noqa: G201
672
675
673
676
async def shell_main (self , subshell_id : str | None , msg ):
674
677
"""Handler of shell messages for a single subshell"""
675
678
if self ._supports_kernel_subshells :
676
679
if subshell_id is None :
677
680
assert threading .current_thread () == threading .main_thread ()
681
+ asyncio_lock = self ._main_asyncio_lock
678
682
else :
679
683
assert threading .current_thread () not in (
680
684
self .shell_channel_thread ,
681
685
threading .main_thread (),
682
686
)
683
- socket_pair = self .shell_channel_thread .manager .get_shell_channel_to_subshell_pair (
684
- subshell_id
685
- )
687
+ asyncio_lock = self .shell_channel_thread .manager .get_subshell_asyncio_lock (
688
+ subshell_id
689
+ )
686
690
else :
687
691
assert subshell_id is None
688
692
assert threading .current_thread () == threading .main_thread ()
689
- socket_pair = None
690
-
691
- try :
692
- # Whilst executing a shell message, do not accept any other shell messages on the
693
- # same subshell, so that cells are run sequentially. Without this we can run multiple
694
- # async cells at the same time which would be a nice feature to have but is an API
695
- # change.
696
- if socket_pair :
697
- socket_pair .pause_on_recv ()
693
+ asyncio_lock = self ._main_asyncio_lock
694
+
695
+ # Whilst executing a shell message, do not accept any other shell messages on the
696
+ # same subshell, so that cells are run sequentially. Without this we can run multiple
697
+ # async cells at the same time which would be a nice feature to have but is an API
698
+ # change.
699
+ assert asyncio_lock is not None
700
+ async with asyncio_lock :
698
701
await self .dispatch_shell (msg , subshell_id = subshell_id )
699
- finally :
700
- if socket_pair :
701
- socket_pair .resume_on_recv ()
702
702
703
703
def record_ports (self , ports ):
704
704
"""Record the ports that this kernel is using.
@@ -739,7 +739,7 @@ def _publish_status(self, status, channel, parent=None):
739
739
def _publish_status_and_flush (self , status , channel , stream , parent = None ):
740
740
"""send status on IOPub and flush specified stream to ensure reply is sent before handling the next reply"""
741
741
self ._publish_status (status , channel , parent )
742
- if stream and hasattr (stream , "flush" ):
742
+ if stream and hasattr (stream , "flush" ) and not self . _supports_kernel_subshells :
743
743
stream .flush (zmq .POLLOUT )
744
744
745
745
def _publish_debug_event (self , event ):
@@ -1382,7 +1382,7 @@ def _abort_queues(self, subshell_id: str | None = None):
1382
1382
1383
1383
# flush streams, so all currently waiting messages
1384
1384
# are added to the queue
1385
- if self .shell_stream :
1385
+ if self .shell_stream and not self . _supports_kernel_subshells :
1386
1386
self .shell_stream .flush ()
1387
1387
1388
1388
# Callback to signal that we are done aborting
0 commit comments