@@ -37,6 +37,8 @@ use crate::wire::comm_info_request::CommInfoRequest;
3737use crate :: wire:: comm_msg:: CommWireMsg ;
3838use crate :: wire:: comm_open:: CommOpen ;
3939use crate :: wire:: exception:: Exception ;
40+ use crate :: wire:: execute_reply:: ExecuteReply ;
41+ use crate :: wire:: execute_request:: ExecuteRequest ;
4042use crate :: wire:: header:: JupyterHeader ;
4143use crate :: wire:: jupyter_message:: JupyterMessage ;
4244use crate :: wire:: jupyter_message:: Message ;
@@ -206,6 +208,10 @@ impl Shell {
206208 ) ;
207209 } ,
208210
211+ CommEvent :: Barrier ( done_tx) => {
212+ done_tx. send ( ( ) ) . log_err ( ) ;
213+ } ,
214+
209215 CommEvent :: Message ( comm_id, msg) => {
210216 let Some ( comm) = self . open_comms . iter ( ) . find ( |c| c. comm_id == comm_id) else {
211217 log:: warn!( "Received message for unknown comm channel {comm_id}: {msg:?}" ) ;
@@ -241,6 +247,14 @@ impl Shell {
241247 /// Process a message received from the front-end, optionally dispatching
242248 /// messages to the IOPub or execution threads
243249 fn process_message ( & mut self , msg : Message ) -> crate :: Result < ( ) > {
250+ // Execute requests get special handling: Shell select-loops on both
251+ // the execute response and comm events, allowing it to process
252+ // backend-initiated comm opens (with barrier handshakes) without
253+ // deadlocking.
254+ if let Message :: ExecuteRequest ( req) = msg {
255+ return self . handle_execute_request ( req) ;
256+ }
257+
244258 // Extract references to the components we need to pass to handlers.
245259 // This allows us to borrow different fields of self independently.
246260 let iopub_tx = & self . iopub_tx ;
@@ -257,13 +271,6 @@ impl Shell {
257271 Message :: IsCompleteRequest ( req) => Self :: handle_request ( iopub_tx, socket, req, |msg| {
258272 block_on ( shell_handler. handle_is_complete_request ( msg) )
259273 } ) ,
260- Message :: ExecuteRequest ( req) => {
261- // FIXME: We should ideally not pass the originator to the language kernel
262- let originator = Originator :: from ( & req) ;
263- Self :: handle_request ( iopub_tx, socket, req, |msg| {
264- block_on ( shell_handler. handle_execute_request ( originator, msg) )
265- } )
266- } ,
267274 Message :: CompleteRequest ( req) => Self :: handle_request ( iopub_tx, socket, req, |msg| {
268275 block_on ( shell_handler. handle_complete_request ( msg) )
269276 } ) ,
@@ -364,6 +371,68 @@ impl Shell {
364371 result. and ( Ok ( ( ) ) )
365372 }
366373
374+ /// Handle an execute request. Unlike other requests that use the generic
375+ /// `handle_request`, this method select-loops on both the execute response
376+ /// and `comm_event_rx`. This allows Shell to process comm events (e.g.
377+ /// `CommEvent::Barrier` from `comm_open_backend`) while the R thread is
378+ /// still executing, preventing a deadlock where the R thread waits for
379+ /// Shell to drain comm events while Shell waits for the execute response.
380+ fn handle_execute_request ( & mut self , req : JupyterMessage < ExecuteRequest > ) -> crate :: Result < ( ) > {
381+ use crossbeam:: channel:: Select ;
382+
383+ self . iopub_tx
384+ . send ( status ( req. clone ( ) , ExecutionState :: Busy ) )
385+ . unwrap ( ) ;
386+
387+ log:: info!( "Received shell request: {req:?}" ) ;
388+
389+ // FIXME: We should ideally not pass the originator to the language kernel
390+ let originator = Originator :: from ( & req) ;
391+ let response_rx = self
392+ . shell_handler
393+ . start_execute_request ( originator, & req. content ) ;
394+
395+ // Select-loop: drain comm events while waiting for the execute reply.
396+ let result = loop {
397+ let mut sel = Select :: new ( ) ;
398+ let resp_idx = sel. recv ( & response_rx) ;
399+ let comm_idx = sel. recv ( & self . comm_event_rx ) ;
400+
401+ let ready = sel. ready ( ) ;
402+ if ready == resp_idx {
403+ // Drain any comm events that arrived before or alongside the response
404+ while let Ok ( event) = self . comm_event_rx . try_recv ( ) {
405+ self . process_comm_event ( event) ;
406+ }
407+ break response_rx. recv ( ) . unwrap ( ) ;
408+ } else if ready == comm_idx {
409+ if let Ok ( event) = self . comm_event_rx . try_recv ( ) {
410+ self . process_comm_event ( event) ;
411+ }
412+ }
413+ } ;
414+
415+ let result = match result {
416+ Ok ( reply) => req. send_reply ( reply, & self . socket ) ,
417+ Err ( crate :: Error :: ShellErrorReply ( error) ) => {
418+ req. send_error :: < ExecuteReply > ( error, & self . socket )
419+ } ,
420+ Err ( crate :: Error :: ShellErrorExecuteReply ( error, exec_count) ) => {
421+ req. send_execute_error ( error, exec_count, & self . socket )
422+ } ,
423+ Err ( err) => {
424+ let error = Exception :: internal_error ( format ! ( "{err:?}" ) ) ;
425+ req. send_error :: < ExecuteReply > ( error, & self . socket )
426+ } ,
427+ } ;
428+
429+ self . iopub_tx
430+ . send ( status ( req. clone ( ) , ExecutionState :: Idle ) )
431+ . unwrap ( ) ;
432+
433+ result. and ( Ok ( ( ) ) )
434+ }
435+
367436 fn handle_notification < Not , Handler > (
368437 iopub_tx : & Sender < IOPubMessage > ,
369438 not : JupyterMessage < Not > ,
0 commit comments