Jeremiah Lowin commited on
Commit
800183b
·
1 Parent(s): 917b76a

Add concurrency test

Browse files
Files changed (1) hide show
  1. tests/client/test_client.py +46 -0
tests/client/test_client.py CHANGED
@@ -342,6 +342,52 @@ async def test_client_nested_context_manager(fastmcp_server):
342
  assert client._session is None
343
 
344
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  async def test_resource_template(fastmcp_server):
346
  """Test using a resource template with InMemoryClient."""
347
  client = Client(transport=FastMCPTransport(fastmcp_server))
 
342
  assert client._session is None
343
 
344
 
345
+ async def test_concurrent_client_context_managers():
346
+ """
347
+ Test that concurrent client usage doesn't cause cross-task cancel scope issues.
348
+ https://github.com/jlowin/fastmcp/pull/643
349
+ """
350
+ # Create a simple server
351
+ server = FastMCP("Test Server")
352
+
353
+ @server.tool()
354
+ def echo(text: str) -> str:
355
+ """Echo tool"""
356
+ return text
357
+
358
+ # Create client
359
+ client = Client(server)
360
+
361
+ # Track results
362
+ results = {}
363
+ errors = []
364
+
365
+ async def use_client(task_id: str, delay: float = 0):
366
+ """Use the client with a small delay to ensure overlap"""
367
+ try:
368
+ async with client:
369
+ # Add a small delay to ensure contexts overlap
370
+ await asyncio.sleep(delay)
371
+ # Make an actual call to exercise the session
372
+ tools = await client.list_tools()
373
+ results[task_id] = len(tools)
374
+ except Exception as e:
375
+ errors.append((task_id, str(e)))
376
+
377
+ # Run multiple tasks concurrently
378
+ # The key is having them enter and exit the context at different times
379
+ await asyncio.gather(
380
+ use_client("task1", 0.0),
381
+ use_client("task2", 0.01), # Slight delay to ensure overlap
382
+ use_client("task3", 0.02),
383
+ return_exceptions=False,
384
+ )
385
+
386
+ assert len(errors) == 0, f"Errors occurred: {errors}"
387
+ assert len(results) == 3
388
+ assert all(count == 1 for count in results.values()) # All should see 1 tool
389
+
390
+
391
  async def test_resource_template(fastmcp_server):
392
  """Test using a resource template with InMemoryClient."""
393
  client = Client(transport=FastMCPTransport(fastmcp_server))