Skip to content

pools: async Pool surface — ModalFlashPool rides Modal's native .aio(); readiness discovers off-loop#68

Open
jvmncs wants to merge 1 commit into
mainfrom
pool-async-surface
Open

pools: async Pool surface — ModalFlashPool rides Modal's native .aio(); readiness discovers off-loop#68
jvmncs wants to merge 1 commit into
mainfrom
pool-async-surface

Conversation

@jvmncs

@jvmncs jvmncs commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

What

  • Pool grows an async surface — gateway_url_async / discover_replicas_async / wake_async — with asyncio.to_thread fallbacks over the sync implementations, so any sync-only pool subclass remains usable from async callers.
  • ModalFlashPool overrides all three natively: the Modal SDK's .aio() interface for gateway resolution and container discovery, and async httpx for the wake fan-out (replacing sync httpx + a ThreadPoolExecutor).
  • service.readiness discovers replicas through the async surface instead of calling blocking pool.discover_replicas() on the caller's event loop.

Why

readiness() is awaited from serving processes' event loops (e.g. a deployment's readiness-poll handler); the inline blocking discovery stalled that loop for the duration of the pool-client round-trips. More generally, async callers previously had to choose between blocking the loop or parking a worker thread just to wait on Modal's own background loop — .aio() is the SDK's intended interface here.

Verification

  • uv run pytest: 52 passed. New pools/base_test.py proves the base fallbacks run sync impls off the loop thread; _replica_urls covered in pools/modal_flash_test.py.
  • Confirmed by probe (read-only, against a live Flash pool in stitch-dev): sync and async surfaces return identical gateway/replica results; NotFoundError parity on an app without the cls; max 21ms event-loop heartbeat gap through real API round-trips. No /wake sent (live run).
  • Modal's own AsyncUsageWarning fires when the sync surface is called from a loop, and its suggested rewrite is exactly the .aio() form adopted here.

…); readiness discovers off-loop

Base Pool grows gateway_url_async/discover_replicas_async/wake_async with
to_thread fallbacks over the sync impls, so any sync-only Pool stays usable
from async callers; ModalFlashPool overrides them with the SDK's .aio() and
async-httpx wake. service.readiness switches to the async discovery, keeping
blocking pool-client I/O off the caller's event loop.

Confirmed by probe (stitch-dev, read-only): sync and async surfaces return
identical gateway/replica results against a live Flash pool; NotFoundError
parity on an app without the cls; no event-loop stall (<25ms heartbeat gap)
during the async calls. Modal's own AsyncUsageWarning fires on the sync
surface in an async context and suggests exactly the .aio rewrite adopted
here. uv run pytest: 52 passed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant