@@ -306,7 +306,7 @@ async def drive() -> None:
306306
307307@pytest .mark .anyio
308308async def test_run_closes_write_stream_on_exit ():
309- """run() enters both streams; the write end is released on EOF."""
309+ """run() owns both streams; the write end is released once the EOF teardown completes ."""
310310 c2s_send , c2s_recv = anyio .create_memory_object_stream [SessionMessage | Exception ](32 )
311311 s2c_send , s2c_recv = anyio .create_memory_object_stream [SessionMessage | Exception ](32 )
312312 server : JSONRPCDispatcher [TransportContext ] = JSONRPCDispatcher (c2s_recv , s2c_send )
@@ -819,29 +819,11 @@ async def test_shutdown_error_response_write_is_bounded_when_the_transport_is_we
819819 caplog : pytest .LogCaptureFixture ,
820820):
821821 """Cancelling the task group hosting run() completes even when the shutdown error write wedges:
822- only `_SHUTDOWN_WRITE_TIMEOUT` releases the join (SDK-defined). The fake stream is needed
823- because run()'s teardown closes a memory stream, which would wake the blocked send."""
824-
825- class WedgedWriteStream :
826- async def send (self , item : SessionMessage ) -> None :
827- await anyio .sleep_forever ()
828-
829- async def aclose (self ) -> None :
830- raise NotImplementedError
831-
832- async def __aenter__ (self ) -> "WedgedWriteStream" :
833- return self
834-
835- async def __aexit__ (
836- self ,
837- exc_type : type [BaseException ] | None ,
838- exc_val : BaseException | None ,
839- exc_tb : TracebackType | None ,
840- ) -> bool | None :
841- return None
842-
822+ only `_SHUTDOWN_WRITE_TIMEOUT` releases the join (SDK-defined). A 0-buffer stream nobody reads
823+ expresses the wedge: run() closes its write stream only after the join, so the send stays parked."""
843824 c2s_send , c2s_recv = anyio .create_memory_object_stream [SessionMessage | Exception ](1 )
844- server : JSONRPCDispatcher [TransportContext ] = JSONRPCDispatcher (c2s_recv , WedgedWriteStream ())
825+ s2c_send , s2c_recv = anyio .create_memory_object_stream [SessionMessage | Exception ](0 )
826+ server : JSONRPCDispatcher [TransportContext ] = JSONRPCDispatcher (c2s_recv , s2c_send )
845827 handler_started = anyio .Event ()
846828
847829 async def park (ctx : DCtx , method : str , params : Mapping [str , Any ] | None ) -> dict [str , Any ]:
@@ -863,19 +845,19 @@ async def on_notify(ctx: DCtx, method: str, params: Mapping[str, Any] | None) ->
863845 await handler_started .wait ()
864846 tg .cancel_scope .cancel ()
865847 finally :
866- c2s_send . close ()
867- c2s_recv .close ()
848+ for s in ( c2s_send , c2s_recv , s2c_send , s2c_recv ):
849+ s .close ()
868850 # The warning proves the bound (not a completed write) released the join.
869851 assert "shutdown error response for request" in caplog .text
870852
871853
872854@pytest .mark .anyio
873855async def test_shutdown_answers_in_flight_request_with_connection_closed ():
874- """Cancelling run() answers a still-running request with CONNECTION_CLOSED (SDK-defined). The
875- recording stream is needed because run()'s exit would close a memory stream before the shielded write lands."""
856+ """Read-stream EOF answers a still-running request with CONNECTION_CLOSED (SDK-defined):
857+ run() keeps the write stream open until the task-group join, so the shielded teardown write lands."""
876858 c2s_send , c2s_recv = anyio .create_memory_object_stream [SessionMessage | Exception ](4 )
877- recording = RecordingWriteStream ( )
878- server : JSONRPCDispatcher [TransportContext ] = JSONRPCDispatcher (c2s_recv , recording )
859+ s2c_send , s2c_recv = anyio . create_memory_object_stream [ SessionMessage | Exception ]( 4 )
860+ server : JSONRPCDispatcher [TransportContext ] = JSONRPCDispatcher (c2s_recv , s2c_send )
879861 handler_started = anyio .Event ()
880862
881863 async def park (ctx : DCtx , method : str , params : Mapping [str , Any ] | None ) -> dict [str , Any ]:
@@ -892,13 +874,16 @@ async def on_notify(ctx: DCtx, method: str, params: Mapping[str, Any] | None) ->
892874 await c2s_send .send (SessionMessage (message = JSONRPCRequest (jsonrpc = "2.0" , id = 1 , method = "t" , params = None )))
893875 with anyio .fail_after (5 ):
894876 await handler_started .wait ()
895- tg .cancel_scope .cancel ()
877+ c2s_send .close () # EOF: run() cancels the parked handler, which must still answer
878+ with anyio .fail_after (5 ):
879+ answer = await s2c_recv .receive ()
880+ assert isinstance (answer , SessionMessage )
881+ assert answer .message == JSONRPCError (
882+ jsonrpc = "2.0" , id = 1 , error = ErrorData (code = CONNECTION_CLOSED , message = "Connection closed" )
883+ )
896884 finally :
897- c2s_send .close ()
898- c2s_recv .close ()
899- assert [m .message for m in recording .sent ] == [
900- JSONRPCError (jsonrpc = "2.0" , id = 1 , error = ErrorData (code = CONNECTION_CLOSED , message = "Connection closed" ))
901- ]
885+ for s in (c2s_send , c2s_recv , s2c_send , s2c_recv ):
886+ s .close ()
902887
903888
904889@pytest .mark .anyio
0 commit comments