@@ -249,6 +249,62 @@ async def drive() -> None:
249249 s2c_recv .close ()
250250
251251
252+ @pytest .mark .anyio
253+ async def test_run_closes_write_stream_after_clean_eof_without_drain_timeout ():
254+ c2s_send , c2s_recv = anyio .create_memory_object_stream [SessionMessage | Exception ](32 )
255+ s2c_send , s2c_recv = anyio .create_memory_object_stream [SessionMessage | Exception ](32 )
256+ server : JSONRPCDispatcher [TransportContext ] = JSONRPCDispatcher (
257+ c2s_recv ,
258+ s2c_send ,
259+ close_write_stream_on_read_close = False ,
260+ read_eof_drain_timeout_seconds = None ,
261+ )
262+ on_request , on_notify = echo_handlers (Recorder ())
263+
264+ with anyio .fail_after (5 ):
265+ async with anyio .create_task_group () as tg , c2s_send , c2s_recv , s2c_send , s2c_recv :
266+ await tg .start (server .run , on_request , on_notify )
267+ c2s_send .close ()
268+ with pytest .raises (anyio .EndOfStream ):
269+ await s2c_recv .receive ()
270+
271+
272+ @pytest .mark .anyio
273+ async def test_run_drains_in_flight_handlers_on_clean_eof_without_timeout ():
274+ c2s_send , c2s_recv = anyio .create_memory_object_stream [SessionMessage | Exception ](32 )
275+ s2c_send , s2c_recv = anyio .create_memory_object_stream [SessionMessage | Exception ](32 )
276+ server : JSONRPCDispatcher [TransportContext ] = JSONRPCDispatcher (
277+ c2s_recv ,
278+ s2c_send ,
279+ close_write_stream_on_read_close = False ,
280+ read_eof_drain_timeout_seconds = None ,
281+ )
282+ handler_started = anyio .Event ()
283+ handler_allowed_to_finish = anyio .Event ()
284+
285+ async def handle_request (ctx : DCtx , method : str , params : Mapping [str , Any ] | None ) -> dict [str , Any ]:
286+ handler_started .set ()
287+ await handler_allowed_to_finish .wait ()
288+ return {"drained" : True }
289+
290+ async def on_notify (ctx : DCtx , method : str , params : Mapping [str , Any ] | None ) -> None :
291+ raise NotImplementedError
292+
293+ with anyio .fail_after (5 ):
294+ async with anyio .create_task_group () as tg , c2s_send , c2s_recv , s2c_send , s2c_recv :
295+ await tg .start (server .run , handle_request , on_notify )
296+ await c2s_send .send (SessionMessage (message = JSONRPCRequest (jsonrpc = "2.0" , id = 1 , method = "x" , params = None )))
297+ await handler_started .wait ()
298+ c2s_send .close ()
299+ handler_allowed_to_finish .set ()
300+
301+ response = await s2c_recv .receive ()
302+ assert isinstance (response , SessionMessage )
303+ assert isinstance (response .message , JSONRPCResponse )
304+ assert response .message .id == 1
305+ assert response .message .result == {"drained" : True }
306+
307+
252308@pytest .mark .anyio
253309async def test_run_closes_write_stream_on_exit ():
254310 """run() enters both streams; the write end is released on EOF."""
0 commit comments