@@ -337,3 +337,74 @@ async def mock_server():
337337 await ev_closed .wait ()
338338 with anyio .fail_after (1 ):
339339 await ev_response .wait ()
340+
341+
342+ @pytest .mark .anyio
343+ async def test_session_aexit_cleanup ():
344+ """Test that the session is closing properly, cleaning up all resources."""
345+ pending_request_ids : list [int | str ] = []
346+ requests_received = anyio .Event ()
347+ client_session_closed = anyio .Event ()
348+
349+ async with (
350+ anyio .create_task_group () as tg ,
351+ create_client_server_memory_streams () as (client_streams , server_streams ),
352+ ):
353+ client_read , client_write = client_streams
354+ server_read , _ = server_streams
355+
356+ async def mock_server ():
357+ """Block responses to simulate a server that does not respond."""
358+ # Wait for two ping requests
359+ for _ in range (2 ):
360+ message = await server_read .receive ()
361+ assert isinstance (message , SessionMessage )
362+ root = message .message .root
363+ assert isinstance (root , JSONRPCRequest )
364+ assert root .method == "ping"
365+ pending_request_ids .append (root .id )
366+
367+ # Signal that both requests have been received
368+ requests_received .set ()
369+
370+ # Wait for the client session to be closed
371+ # This ensures the cleanup logic in finally block has time to run
372+ await client_session_closed .wait ()
373+
374+ async def send_ping (session : ClientSession ):
375+ # Since we are closing the session, "Connection closed" McpError is expected
376+ with pytest .raises (McpError ) as e :
377+ await session .send_ping ()
378+ assert "Connection closed" in str (e .value )
379+
380+ # Start the mock server in the background
381+ tg .start_soon (mock_server )
382+
383+ # Create a session and send multiple ping requests in background
384+ async with ClientSession (read_stream = client_read , write_stream = client_write ) as session :
385+ # Verify initial state
386+ assert len (session ._response_streams ) == 0
387+
388+ # Start two ping requests in background
389+ tg .start_soon (send_ping , session )
390+ tg .start_soon (send_ping , session )
391+
392+ # Wait for both requests to be sent and received by server
393+ await requests_received .wait ()
394+ await anyio .sleep (0.1 ) # Give time for streams to be created
395+
396+ # Verify we have 2 response streams
397+ assert len (session ._response_streams ) == 2
398+
399+ # We close the session by escaping the async with block
400+ client_session_closed .set ()
401+
402+ # Since the sesssion has been closed, "Connection closed" McpError is expected
403+ with pytest .raises (McpError ) as e :
404+ await session .send_ping ()
405+ assert "Connection closed" in str (e .value )
406+
407+ # Verify all response streams have been cleaned up
408+ # (This happens when the async with block exits and __aexit__ is called)
409+ assert session is not None
410+ assert len (session ._response_streams ) == 0
0 commit comments