@@ -59,62 +59,25 @@ def _validate_lifecycle_state(self, proposal: dict):
5959 raise TraitError (f"lifecycle_state must be one of { LifecycleStates } " )
6060 return value
6161
62- state = Dict ()
63-
64- @default ('state' )
65- def _default_state (self ):
66- return {
67- "execution_state" : self .execution_state ,
68- "lifecycle_state" : self .lifecycle_state
69- }
70-
71- @observe ('execution_state' )
72- def _observer_execution_state (self , change ):
73- state = self .state
74- state ["execution_state" ] = change ['new' ]
75- self .state = state
76-
77- @observe ('lifecycle_state' )
78- def _observer_lifecycle_state (self , change ):
79- state = self .state
80- state ["lifecycle_state" ] = change ['new' ]
81- self .state = state
82-
83- @validate ('state' )
84- def _validate_state (self , change ):
85- value = change ['value' ]
86- if 'execution_state' not in value or 'lifecycle_state' not in value :
87- TraitError ("State needs to include execution_state and lifecycle_state" )
88- return value
89-
90- @observe ('state' )
91- def _state_changed (self , change ):
92- for observer in self ._state_observers :
93- observer (change ["new" ])
94-
95- _state_observers = Set (allow_none = True )
96-
9762 def set_state (
9863 self ,
9964 lifecycle_state : LifecycleStates = None ,
10065 execution_state : ExecutionStates = None ,
101- broadcast = True
10266 ):
10367 if lifecycle_state :
10468 self .lifecycle_state = lifecycle_state .value
10569 if execution_state :
10670 self .execution_state = execution_state .value
107-
108- if broadcast :
109- # Broadcast this state change to all listeners
110- self ._state_observers = None
111- self .broadcast_state ()
11271
11372 async def start_kernel (self , * args , ** kwargs ):
11473 self .set_state (LifecycleStates .STARTING , ExecutionStates .STARTING )
11574 out = await super ().start_kernel (* args , ** kwargs )
11675 self .set_state (LifecycleStates .STARTED )
117- await self .connect ()
76+ # Schedule the kernel to connect.
77+ # Do not await here, since many clients expect
78+ # the server to complete the start flow even
79+ # if the kernel is not fully connected yet.
80+ task = asyncio .create_task (self .connect ())
11881 return out
11982
12083 async def shutdown_kernel (self , * args , ** kwargs ):
@@ -137,12 +100,13 @@ async def connect(self):
137100 be in a starting phase. We can keep a connection
138101 open regardless if the kernel is ready.
139102 """
140- self .set_state (LifecycleStates .CONNECTING , ExecutionStates .BUSY )
141103 # Use the new API for getting a client.
142104 self .main_client = self .client ()
143105 # Track execution state by watching all messages that come through
144106 # the kernel client.
145107 self .main_client .add_listener (self .execution_state_listener )
108+ self .set_state (LifecycleStates .CONNECTING , ExecutionStates .STARTING )
109+ await self .broadcast_state ()
146110 self .main_client .start_channels ()
147111 await self .main_client .start_listening ()
148112 # The Heartbeat channel is paused by default; unpause it here
@@ -157,33 +121,34 @@ async def connect(self):
157121 raise Exception ("The kernel took too long to connect to the ZMQ sockets." )
158122 # Wait a second until the next time we try again.
159123 await asyncio .sleep (1 )
160- # Send an initial kernel info request on the shell channel.
161- self .main_client .send_kernel_info ()
162- self .set_state (LifecycleStates .CONNECTED )
124+ # Wait for the kernel to reach an idle state.
125+ while self .execution_state != ExecutionStates .IDLE .value :
126+ self .main_client .send_kernel_info ()
127+ await asyncio .sleep (1 )
163128
164129 async def disconnect (self ):
165130 await self .main_client .stop_listening ()
166131 self .main_client .stop_channels ()
167132
168- def broadcast_state (self ):
133+ async def broadcast_state (self ):
169134 """Broadcast state to all listeners"""
170135 if not self .main_client :
171136 return
172-
173- # Emit this state to all listeners
174- for listener in self .main_client ._listeners :
175- # Manufacture a status message
176- session = self . main_client . session
177- msg = session . msg ( "status" , { "execution_state" : self . execution_state })
178- msg = session .serialize ( msg )
179- _ , fed_msg_list = self . session .feed_identities (msg )
180- listener ("iopub" , fed_msg_list )
137+
138+ # Manufacture an IOPub status message from the shell channel.
139+ session = self .main_client .session
140+ parent_header = session . msg_header ( " status" )
141+ parent_msg_id = parent_header [ "msg_id" ]
142+ self . main_client . message_source_cache [ parent_msg_id ] = "shell"
143+ msg = session .msg ( "status" , content = { "execution_state" : self . execution_state }, parent = parent_header )
144+ smsg = session .serialize (msg )[ 1 :]
145+ await self . main_client . handle_outgoing_message ("iopub" , smsg )
181146
182147 def execution_state_listener (self , channel_name : str , msg : list [bytes ]):
183148 """Set the execution state by watching messages returned by the shell channel."""
184149 # Only continue if we're on the IOPub where the status is published.
185150 if channel_name != "iopub" :
186- return
151+ return
187152
188153 session = self .main_client .session
189154 # Unpack the message
@@ -193,22 +158,18 @@ def execution_state_listener(self, channel_name: str, msg: list[bytes]):
193158 execution_state = content ["execution_state" ]
194159 if execution_state == "starting" :
195160 # Don't broadcast, since this message is already going out.
196- self .set_state (LifecycleStates . STARTING , ExecutionStates .STARTING , broadcast = False )
161+ self .set_state (execution_state = ExecutionStates .STARTING )
197162 else :
198163 parent = deserialized_msg .get ("parent_header" , {})
199164 msg_id = parent .get ("msg_id" , "" )
200165 parent_channel = self .main_client .message_source_cache .get (msg_id , None )
201166 if parent_channel and parent_channel == "shell" :
202- # Don't broadcast, since this message is already going out.
203- self .set_state (LifecycleStates .CONNECTED , ExecutionStates (execution_state ), broadcast = False )
204-
167+ self .set_state (LifecycleStates .CONNECTED , ExecutionStates (execution_state ))
168+
205169 kernel_status = {
206170 "execution_state" : self .execution_state ,
207171 "lifecycle_state" : self .lifecycle_state
208- }
172+ }
209173 self .log .debug (f"Sending kernel status awareness { kernel_status } " )
210174 self .main_client .send_kernel_awareness (kernel_status )
211- self .log .debug (f"Sent kernel status awareness { kernel_status } " )
212-
213-
214-
175+ self .log .debug (f"Sent kernel status awareness { kernel_status } " )
0 commit comments