Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions loadtest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
58 changes: 58 additions & 0 deletions loadtest/pool_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""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 sys
import resource

from runloop_api_client import Runloop

HOST = "api.runloop.ai"
N = 20


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 _ 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[int] = 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()