@@ -35,6 +35,13 @@ class StreamEvent:
3535 message : dict [str , Any ]
3636
3737
38+ @dataclass (slots = True )
39+ class _RequestNotificationState :
40+ start_sequence : int
41+ barrier : asyncio .Future [None ]
42+ response_received : bool = False
43+
44+
3845StreamObserver = Callable [[StreamEvent ], Awaitable [None ] | None ]
3946
4047
@@ -58,7 +65,7 @@ def __init__(
5865 # response cannot overtake notifications received during that request.
5966 self ._notification_sequence = 0
6067 self ._pending_notifications : dict [int , asyncio .Future [None ]] = {}
61- self ._request_notification_starts : dict [int , int ] = {}
68+ self ._request_notifications : dict [int , _RequestNotificationState ] = {}
6269 self ._tasks = TaskSupervisor (source = "acp.Connection" )
6370 self ._tasks .add_error_handler (self ._on_task_error )
6471 self ._closed = False
@@ -89,11 +96,13 @@ async def close(self) -> None:
8996 if self ._closed :
9097 return
9198 self ._closed = True
99+ self ._release_request_barriers (response_received = False )
92100 self ._reject_all_outgoing (ConnectionError ("Connection closed" ))
93101 try :
94102 await self ._transport .close ()
95103 finally :
96104 await self ._tasks .shutdown ()
105+ self ._release_request_barriers ()
97106
98107 async def main_loop (self ) -> None :
99108 try :
@@ -117,26 +126,31 @@ async def send_request(self, method: str, params: JsonValue | None = None) -> An
117126 self ._raise_if_unavailable ()
118127 request_id = self ._next_request_id
119128 self ._next_request_id += 1
120- self ._request_notification_starts [request_id ] = self ._notification_sequence
129+ notification_state = _RequestNotificationState (
130+ start_sequence = self ._notification_sequence ,
131+ barrier = asyncio .get_running_loop ().create_future (),
132+ )
133+ self ._request_notifications [request_id ] = notification_state
121134 future : asyncio .Future [Any ] = asyncio .get_running_loop ().create_future ()
122135 self ._pending [request_id ] = future
123136 payload = {"jsonrpc" : "2.0" , "id" : request_id , "method" : method , "params" : params }
124137 try :
125138 await self ._transport .send (payload )
126139 except BaseException :
127- self ._request_notification_starts .pop (request_id , None )
140+ self ._request_notifications .pop (request_id , None )
128141 self ._pending .pop (request_id , None )
129142 future .cancel ()
130143 raise
131144 self ._notify_observers (StreamDirection .OUTGOING , payload )
132145 try :
146+ await notification_state .barrier
133147 return await future
134148 except asyncio .CancelledError :
135149 self ._pending .pop (request_id , None )
136150 future .cancel ()
137151 raise
138152 finally :
139- self ._request_notification_starts .pop (request_id , None )
153+ self ._request_notifications .pop (request_id , None )
140154
141155 async def send_notification (self , method : str , params : JsonValue | None = None ) -> None :
142156 self ._raise_if_unavailable ()
@@ -179,29 +193,41 @@ def _process_message(self, message: dict[str, Any]) -> None:
179193 return
180194 if has_id : # this is a response, {"id", "result" | "error"}
181195 request_id = message ["id" ]
196+ notification_state = self ._request_notifications .get (request_id )
197+ if notification_state is None :
198+ self ._handle_response (message )
199+ return
182200 # Excluding notifications received before this request began keeps
183201 # notification handlers free to make nested requests without those
184202 # responses waiting on the handler that issued them.
185- start_sequence = self ._request_notification_starts .get (request_id , self ._notification_sequence )
186203 preceding_notifications = tuple (
187- completion for sequence , completion in self ._pending_notifications .items () if sequence > start_sequence
204+ completion
205+ for sequence , completion in self ._pending_notifications .items ()
206+ if sequence > notification_state .start_sequence
188207 )
208+ # Resolve the stored response before waiting. Otherwise EOF can
209+ # reject a response that was already received while its preceding
210+ # notification handler is still running.
211+ self ._handle_response (message )
212+ notification_state .response_received = True
189213 if preceding_notifications :
190214 self ._tasks .create (
191- self ._handle_response_after_notifications (message , preceding_notifications ),
192- name = "acp.Connection.response" ,
193- on_error = self ._on_receive_error ,
215+ self ._release_response_after_notifications (notification_state , preceding_notifications ),
216+ name = "acp.Connection.response-barrier" ,
194217 )
195- else :
196- self . _handle_response ( message )
218+ elif not notification_state . barrier . done () :
219+ notification_state . barrier . set_result ( None )
197220
198- async def _handle_response_after_notifications (
221+ async def _release_response_after_notifications (
199222 self ,
200- message : dict [ str , Any ] ,
223+ notification_state : _RequestNotificationState ,
201224 preceding_notifications : tuple [asyncio .Future [None ], ...],
202225 ) -> None :
203- await asyncio .gather (* (asyncio .shield (completion ) for completion in preceding_notifications ))
204- self ._handle_response (message )
226+ try :
227+ await asyncio .gather (* (asyncio .shield (completion ) for completion in preceding_notifications ))
228+ finally :
229+ if not notification_state .barrier .done ():
230+ notification_state .barrier .set_result (None )
205231
206232 async def _run_tracked_notification (
207233 self ,
@@ -313,6 +339,7 @@ def _disconnect(self) -> None:
313339 if self ._disconnected :
314340 return
315341 self ._disconnected = True
342+ self ._release_request_barriers (response_received = False )
316343 self ._reject_all_outgoing (ConnectionError ("Connection closed" ))
317344
318345 def _reject_all_outgoing (self , error : BaseException ) -> None :
@@ -322,6 +349,13 @@ def _reject_all_outgoing(self, error: BaseException) -> None:
322349 if not future .done ():
323350 future .set_exception (error )
324351
352+ def _release_request_barriers (self , * , response_received : bool | None = None ) -> None :
353+ for state in self ._request_notifications .values ():
354+ if response_received is not None and state .response_received is not response_received :
355+ continue
356+ if not state .barrier .done ():
357+ state .barrier .set_result (None )
358+
325359 def _raise_if_unavailable (self ) -> None :
326360 if self ._disconnected or self ._closed :
327361 raise ConnectionError ("Connection closed" )
0 commit comments