@@ -55,6 +55,13 @@ class StreamEvent:
5555 message : dict [str , Any ]
5656
5757
58+ @dataclass (slots = True )
59+ class _RequestNotificationState :
60+ start_sequence : int
61+ barrier : asyncio .Future [None ]
62+ response_received : bool = False
63+
64+
5865StreamObserver = Callable [[StreamEvent ], Awaitable [None ] | None ]
5966
6067
@@ -81,7 +88,7 @@ def __init__(
8188 # response cannot overtake notifications received during that request.
8289 self ._notification_sequence = 0
8390 self ._pending_notifications : dict [int , asyncio .Future [None ]] = {}
84- self ._request_notification_starts : dict [int , int ] = {}
91+ self ._request_notifications : dict [int , _RequestNotificationState ] = {}
8592 self ._state = state_store or InMemoryMessageStateStore ()
8693 self ._tasks = TaskSupervisor (source = "acp.Connection" )
8794 self ._tasks .add_error_handler (self ._on_task_error )
@@ -126,6 +133,7 @@ async def close(self) -> None:
126133 await self ._dispatcher .stop ()
127134 await self ._transport .close ()
128135 await self ._tasks .shutdown ()
136+ self ._release_request_barriers ()
129137 self ._state .reject_all_outgoing (ConnectionError ("Connection closed" ))
130138
131139 async def main_loop (self ) -> None :
@@ -150,7 +158,11 @@ async def send_request(self, method: str, params: JsonValue | None = None) -> An
150158 self ._raise_if_unavailable ()
151159 request_id = self ._next_request_id
152160 self ._next_request_id += 1
153- self ._request_notification_starts [request_id ] = self ._notification_sequence
161+ notification_state = _RequestNotificationState (
162+ start_sequence = self ._notification_sequence ,
163+ barrier = asyncio .get_running_loop ().create_future (),
164+ )
165+ self ._request_notifications [request_id ] = notification_state
154166 future = self ._state .register_outgoing (request_id , method )
155167 payload = {"jsonrpc" : "2.0" , "id" : request_id , "method" : method , "params" : params }
156168 try :
@@ -159,14 +171,18 @@ async def send_request(self, method: str, params: JsonValue | None = None) -> An
159171 # A synchronous send failure (e.g. HTTP POST rejected before any
160172 # JSON-RPC response exists) must reject the correlated future so the
161173 # caller gets a real, attributable error.
162- self ._request_notification_starts .pop (request_id , None )
174+ self ._request_notifications .pop (request_id , None )
163175 self ._state .reject_outgoing (request_id , exc )
164176 raise
165177 self ._notify_observers (StreamDirection .OUTGOING , payload )
166178 try :
179+ await notification_state .barrier
167180 return await future
181+ except asyncio .CancelledError :
182+ future .cancel ()
183+ raise
168184 finally :
169- self ._request_notification_starts .pop (request_id , None )
185+ self ._request_notifications .pop (request_id , None )
170186
171187 async def send_notification (self , method : str , params : JsonValue | None = None ) -> None :
172188 self ._raise_if_unavailable ()
@@ -204,29 +220,41 @@ async def _process_message(self, message: dict[str, Any]) -> None:
204220 return
205221 if has_id :
206222 request_id = message ["id" ]
223+ notification_state = self ._request_notifications .get (request_id )
224+ if notification_state is None :
225+ await self ._handle_response (message )
226+ return
207227 # Excluding notifications received before this request began keeps
208228 # notification handlers free to make nested requests without those
209229 # responses waiting on the handler that issued them.
210- start_sequence = self ._request_notification_starts .get (request_id , self ._notification_sequence )
211230 preceding_notifications = tuple (
212- completion for sequence , completion in self ._pending_notifications .items () if sequence > start_sequence
231+ completion
232+ for sequence , completion in self ._pending_notifications .items ()
233+ if sequence > notification_state .start_sequence
213234 )
235+ # Resolve the stored response before waiting. Otherwise EOF can
236+ # reject a response that was already received while its preceding
237+ # notification handler is still running.
238+ await self ._handle_response (message )
239+ notification_state .response_received = True
214240 if preceding_notifications :
215241 self ._tasks .create (
216- self ._handle_response_after_notifications (message , preceding_notifications ),
217- name = "acp.Connection.response" ,
218- on_error = self ._on_receive_error ,
242+ self ._release_response_after_notifications (notification_state , preceding_notifications ),
243+ name = "acp.Connection.response-barrier" ,
219244 )
220- else :
221- await self . _handle_response ( message )
245+ elif not notification_state . barrier . done () :
246+ notification_state . barrier . set_result ( None )
222247
223- async def _handle_response_after_notifications (
248+ async def _release_response_after_notifications (
224249 self ,
225- message : dict [ str , Any ] ,
250+ notification_state : _RequestNotificationState ,
226251 preceding_notifications : tuple [asyncio .Future [None ], ...],
227252 ) -> None :
228- await asyncio .gather (* (asyncio .shield (completion ) for completion in preceding_notifications ))
229- await self ._handle_response (message )
253+ try :
254+ await asyncio .gather (* (asyncio .shield (completion ) for completion in preceding_notifications ))
255+ finally :
256+ if not notification_state .barrier .done ():
257+ notification_state .barrier .set_result (None )
230258
231259 def _notify_observers (self , direction : StreamDirection , message : dict [str , Any ]) -> None :
232260 if not self ._observers :
@@ -357,8 +385,16 @@ def _disconnect(self) -> None:
357385 if self ._disconnected :
358386 return
359387 self ._disconnected = True
388+ self ._release_request_barriers (response_received = False )
360389 self ._state .reject_all_outgoing (ConnectionError ("Connection closed" ))
361390
391+ def _release_request_barriers (self , * , response_received : bool | None = None ) -> None :
392+ for state in self ._request_notifications .values ():
393+ if response_received is not None and state .response_received is not response_received :
394+ continue
395+ if not state .barrier .done ():
396+ state .barrier .set_result (None )
397+
362398 def _raise_if_unavailable (self ) -> None :
363399 if self ._disconnected or self ._closed :
364400 raise ConnectionError ("Connection closed" )
0 commit comments