| |
| """Concurrency smoke test for the MatrixLab MCP sandbox worker. |
| |
| Starts N sandbox sessions in parallel against a running HF Space worker, |
| waits for each to reach ``running``, asserts tools were discovered, then |
| tears them all down. Use it to validate that an instance can sustain the |
| configured ``MATRIXLAB_MCP_MAX_SESSIONS`` (e.g. 10) in parallel. |
| |
| Usage: |
| BASE=http://127.0.0.1:7901 N=10 python hf/scripts/sandbox_concurrency_smoke.py |
| """ |
| from __future__ import annotations |
|
|
| import asyncio |
| import os |
| import sys |
| import time |
|
|
| import httpx |
|
|
| BASE = os.environ.get("BASE", "http://127.0.0.1:7901").rstrip("/") |
| N = int(os.environ.get("N", "10")) |
| TTL = int(os.environ.get("TTL", "120")) |
| TOKEN = os.environ.get("MATRIXLAB_SANDBOX_TOKEN", "") |
| PLAN = { |
| "entity_id": "mcp_server:filesystem", |
| "runtime": "node", |
| "start_command": "npx -y @modelcontextprotocol/server-filesystem /tmp", |
| "transport": "stdio", |
| "ttl_seconds": TTL, |
| } |
| HEADERS = {"Authorization": f"Bearer {TOKEN}"} if TOKEN else {} |
|
|
|
|
| async def start_one(client: httpx.AsyncClient, i: int) -> dict: |
| r = await client.post(f"{BASE}/mcp/sessions", json={**PLAN, "entity_id": f"sbx-{i}"}, headers=HEADERS) |
| r.raise_for_status() |
| return r.json() |
|
|
|
|
| async def wait_running(client: httpx.AsyncClient, sid: str, timeout: int = 120) -> dict: |
| deadline = time.time() + timeout |
| last = {} |
| while time.time() < deadline: |
| r = await client.get(f"{BASE}/mcp/sessions/{sid}", headers=HEADERS) |
| last = r.json() |
| st = last.get("status") |
| if st in ("running", "failed", "timeout", "install_failed", "start_failed", "mcp_failed"): |
| return last |
| await asyncio.sleep(1.5) |
| return last |
|
|
|
|
| async def main() -> int: |
| async with httpx.AsyncClient(timeout=30) as client: |
| h = (await client.get(f"{BASE}/mcp/health", headers=HEADERS)).json() |
| print(f"worker: max_sessions={h['max_sessions']} instance={h.get('instance')}") |
| if h["max_sessions"] < N: |
| print(f"NOTE: max_sessions ({h['max_sessions']}) < N ({N}); " |
| f"set MATRIXLAB_MCP_MAX_SESSIONS={N} (or 'auto') to admit all.") |
|
|
| t0 = time.time() |
| started = await asyncio.gather(*[start_one(client, i) for i in range(N)], return_exceptions=True) |
| ok_started = [s for s in started if isinstance(s, dict)] |
| rejected = [s for s in started if not isinstance(s, dict)] |
| print(f"requested {N} 路 admitted {len(ok_started)} 路 rejected {len(rejected)} " |
| f"in {time.time()-t0:.1f}s") |
|
|
| results = await asyncio.gather(*[wait_running(client, s["session_id"]) for s in ok_started]) |
| running = [r for r in results if r.get("status") == "running"] |
| with_tools = [r for r in running if (r.get("tools") or [])] |
| elapsed = time.time() - t0 |
|
|
| print("\nper-session:") |
| for r in results: |
| print(f" {r.get('entity_id','?'):10} {r.get('status','?'):10} " |
| f"tools={len(r.get('tools') or [])}") |
|
|
| print(f"\nrunning {len(running)}/{len(ok_started)} 路 with tools {len(with_tools)} " |
| f"路 wall {elapsed:.1f}s") |
|
|
| |
| await asyncio.gather(*[client.delete(f"{BASE}/mcp/sessions/{s['session_id']}", headers=HEADERS) |
| for s in ok_started], return_exceptions=True) |
| print("torn down.") |
|
|
| ok = len(running) == len(ok_started) and len(with_tools) == len(running) and len(ok_started) >= 1 |
| print("RESULT:", "PASS" if ok else "FAIL") |
| return 0 if ok else 1 |
|
|
|
|
| if __name__ == "__main__": |
| sys.exit(asyncio.run(main())) |
|
|