From e50d99157498567fda1d02fb23a72669087d1398 Mon Sep 17 00:00:00 2001 From: Yoon Park Date: Sun, 12 Jul 2026 15:28:03 -0700 Subject: [PATCH 1/5] feat(loadtest): add pool_check.py to verify shared sync connection pool Creates N Runloop instances and asserts they all reference the same underlying httpx transport object, confirming no per-instance FD growth. Co-Authored-By: Claude Sonnet 4.6 --- loadtest/pool_check.py | 72 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 loadtest/pool_check.py diff --git a/loadtest/pool_check.py b/loadtest/pool_check.py new file mode 100644 index 000000000..fbf3064a6 --- /dev/null +++ b/loadtest/pool_check.py @@ -0,0 +1,72 @@ +"""Verify that multiple Runloop (sync) instances share a single connection pool. + +Spins up N SDK instances and checks that the number of open connections to +api.runloop.ai does not grow linearly with instance count — it should stay at +one (or a small fixed number) because all instances share the same underlying +httpx transport. + +Usage: + uv run python loadtest/pool_check.py + +No API key is required — we only check the transport object identity and the +OS-level connection count, not make real requests. +""" + +from __future__ import annotations + +import os +import resource +import subprocess +import sys + +from runloop_api_client import Runloop + +HOST = "api.runloop.ai" +N = 20 + + +def open_connections_to(host: str) -> int: + """Count ESTABLISHED TCP connections to *host* via lsof.""" + try: + out = subprocess.check_output( + ["lsof", "-i", f"@{host}", "-sTCP:ESTABLISHED", "-n", "-P"], + stderr=subprocess.DEVNULL, + text=True, + ) + return max(0, len(out.strip().splitlines()) - 1) # subtract header + except Exception: + return -1 # lsof unavailable + + +def main() -> None: + fd_before = resource.getrlimit(resource.RLIMIT_NOFILE)[0] + print(f"FD limit: {fd_before}") + print(f"Creating {N} Runloop instances...\n") + + clients: list[Runloop] = [] + for i in range(N): + clients.append(Runloop(bearer_token=os.environ.get("RUNLOOP_API_KEY", "dummy"))) + + # All instances should reference the same shared transport object. + transport_ids = set() + for c in clients: + t = getattr(c._client, "_transport", None) + if t is not None: + transport_ids.add(id(t)) + + print(f"Distinct transport objects across {N} instances: {len(transport_ids)}") + if len(transport_ids) == 1: + print("PASS — all instances share one transport (connection pool).") + else: + print("FAIL — instances have separate transports; FD exhaustion is possible.") + sys.exit(1) + + # Close all clients. + for c in clients: + c.close() + + print("\nDone.") + + +if __name__ == "__main__": + main() From 6b2233921684cdb5b06128096177b79e1fc018a2 Mon Sep 17 00:00:00 2001 From: Yoon Park Date: Mon, 13 Jul 2026 08:46:10 -0700 Subject: [PATCH 2/5] docs(loadtest): add pool_check.py to README and remove dead open_connections_to helper Co-Authored-By: Claude Sonnet 4.6 --- loadtest/README.md | 5 +++++ loadtest/pool_check.py | 12 ------------ 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/loadtest/README.md b/loadtest/README.md index cf3e4669d..9e85f5ce2 100644 --- a/loadtest/README.md +++ b/loadtest/README.md @@ -20,11 +20,16 @@ client + server _request handling_ from provisioning. | `h2_single_conn.py` | Raw `httpx` HTTP/2 on a single warmed connection (50-request burst). | | `raw_fetch_test.py` | Raw `httpx` HTTP/1.1 keep-alive baseline. | | `alpn_check.py` | Confirms the origin negotiates `h2` via TLS ALPN. | +| `pool_check.py` | Verifies that multiple sync `Runloop` instances share a single connection pool (transport object identity check, no real requests). | The raw-transport probes compare httpx HTTP/2 multiplexing against HTTP/1.1 directly — the same comparison that motivates the SDK's shared HTTP/2 pool. They're kept so the comparison stays reproducible. +`pool_check.py` is a lightweight sanity check (no API key, no real requests) that +confirms multiple sync `Runloop` instances reuse a single underlying transport +object — preventing file-descriptor exhaustion when many clients are instantiated. + ```sh # Install the SDK from this checkout (editable): cd /path/to/api-client-python && uv sync diff --git a/loadtest/pool_check.py b/loadtest/pool_check.py index fbf3064a6..3d53a5a72 100644 --- a/loadtest/pool_check.py +++ b/loadtest/pool_check.py @@ -25,18 +25,6 @@ N = 20 -def open_connections_to(host: str) -> int: - """Count ESTABLISHED TCP connections to *host* via lsof.""" - try: - out = subprocess.check_output( - ["lsof", "-i", f"@{host}", "-sTCP:ESTABLISHED", "-n", "-P"], - stderr=subprocess.DEVNULL, - text=True, - ) - return max(0, len(out.strip().splitlines()) - 1) # subtract header - except Exception: - return -1 # lsof unavailable - def main() -> None: fd_before = resource.getrlimit(resource.RLIMIT_NOFILE)[0] From 19a23a8497862f0d0b34c57857da8597087f2d50 Mon Sep 17 00:00:00 2001 From: Yoon Park Date: Mon, 13 Jul 2026 10:18:52 -0700 Subject: [PATCH 3/5] fix(loadtest): resolve pyright lint errors in pool_check.py Co-Authored-By: Claude Sonnet 4.6 --- loadtest/pool_check.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/loadtest/pool_check.py b/loadtest/pool_check.py index 3d53a5a72..955d5df20 100644 --- a/loadtest/pool_check.py +++ b/loadtest/pool_check.py @@ -16,7 +16,6 @@ import os import resource -import subprocess import sys from runloop_api_client import Runloop @@ -32,11 +31,11 @@ def main() -> None: print(f"Creating {N} Runloop instances...\n") clients: list[Runloop] = [] - for i in range(N): + for _ in range(N): clients.append(Runloop(bearer_token=os.environ.get("RUNLOOP_API_KEY", "dummy"))) # All instances should reference the same shared transport object. - transport_ids = set() + transport_ids: set[int] = set() for c in clients: t = getattr(c._client, "_transport", None) if t is not None: From e3ce77a73d1b420cb2fe9cdec5aad43242c759ec Mon Sep 17 00:00:00 2001 From: Yoon Park Date: Mon, 13 Jul 2026 10:30:12 -0700 Subject: [PATCH 4/5] fix(loadtest): sort imports in pool_check.py Co-Authored-By: Claude Sonnet 4.6 --- loadtest/pool_check.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loadtest/pool_check.py b/loadtest/pool_check.py index 955d5df20..954043407 100644 --- a/loadtest/pool_check.py +++ b/loadtest/pool_check.py @@ -15,8 +15,8 @@ from __future__ import annotations import os -import resource import sys +import resource from runloop_api_client import Runloop From c2c1b1a851fbb5ed9954bba087876d31068e20d3 Mon Sep 17 00:00:00 2001 From: Yoon Park Date: Mon, 13 Jul 2026 10:35:29 -0700 Subject: [PATCH 5/5] fix(loadtest): format pool_check.py Co-Authored-By: Claude Sonnet 4.6 --- loadtest/pool_check.py | 1 - 1 file changed, 1 deletion(-) diff --git a/loadtest/pool_check.py b/loadtest/pool_check.py index 954043407..966857c57 100644 --- a/loadtest/pool_check.py +++ b/loadtest/pool_check.py @@ -24,7 +24,6 @@ N = 20 - def main() -> None: fd_before = resource.getrlimit(resource.RLIMIT_NOFILE)[0] print(f"FD limit: {fd_before}")