Skip to content

Fix: use httpx.AsyncClient in async FastAPI handlers to prevent event loop blocking (fixes #559) - #570

Open
SoulSniper-V2 wants to merge 3 commits into
open-jarvis:mainfrom
SoulSniper-V2:fix-async-handlers-559
Open

Fix: use httpx.AsyncClient in async FastAPI handlers to prevent event loop blocking (fixes #559)#570
SoulSniper-V2 wants to merge 3 commits into
open-jarvis:mainfrom
SoulSniper-V2:fix-async-handlers-559

Conversation

@SoulSniper-V2

Copy link
Copy Markdown
Contributor

What does this PR do?

This PR resolves issue #559 by converting several blocking, synchronous httpx.Client() calls inside async def FastAPI routes into their httpx.AsyncClient() equivalents. It also offloads the CPU-bound backend.transcribe() call in api_routes.py to a separate thread via asyncio.to_thread(). This prevents the Uvicorn event loop from freezing and causing poor latency for other parallel requests.

Changes

  • Replaced httpx.Client with async with httpx.AsyncClient in server/routes.py (/v1/models/pull, /v1/models/{name}).
  • Replaced httpx.get/post with async with httpx.AsyncClient in server/agent_manager_routes.py (/verify, /register-webhook, /test).
  • Offloaded Whisper transcription in server/api_routes.py to asyncio.to_thread.
  • Added a regression test tests/server/test_async_handlers_nonblocking.py to verify event loop non-blocking behavior.

Testing

  • Added unit/integration tests
  • Ran locally
  • Verified endpoints

Fixes Issue

Fixes #559

Copilot AI review requested due to automatic review settings June 19, 2026 19:32

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses #559 by removing blocking I/O and CPU work from FastAPI async routes, primarily by switching outbound HTTP calls to httpx.AsyncClient and offloading synchronous transcription work to a thread to keep the Uvicorn event loop responsive.

Changes:

  • Converted Ollama model pull/delete routes to use httpx.AsyncClient instead of httpx.Client.
  • Converted SendBlue verify/register/test routes to use httpx.AsyncClient instead of httpx.get/post.
  • Offloaded backend.transcribe() in the speech transcription route using asyncio.to_thread(), and added a regression test for non-blocking behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
tests/server/test_async_handlers_nonblocking.py Adds a regression test intended to verify the event loop remains responsive during slow outbound requests.
src/openjarvis/server/routes.py Uses httpx.AsyncClient in async model pull/delete handlers to avoid blocking the event loop.
src/openjarvis/server/api_routes.py Runs synchronous speech transcription in a thread (asyncio.to_thread) to avoid blocking.
src/openjarvis/server/agent_manager_routes.py Uses httpx.AsyncClient in async SendBlue routes to avoid blocking the event loop.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +57 to +60
# Fire both requests concurrently
slow_req = asyncio.create_task(
client.post("/v1/models/pull", json={"name": "test-model"})
)
Comment on lines +52 to +54
with pytest.MonkeyPatch.context() as m:
m.setattr("httpx.AsyncClient.post", mock_post)

Comment on lines 762 to +771
import httpx as _httpx

host = getattr(engine, "_host", "http://localhost:11434")
client = _httpx.Client(base_url=host, timeout=600.0)
try:
resp = client.post(
"/api/pull",
json={"name": model_name, "stream": False},
)
resp.raise_for_status()
async with _httpx.AsyncClient(base_url=host, timeout=600.0) as client:
resp = await client.post(
"/api/pull",
json={"name": model_name, "stream": False},
)
resp.raise_for_status()
jonsaadfalcon pushed a commit that referenced this pull request Jul 1, 2026
Closes #219. Replace synchronous httpx calls in async SendBlue and model-management handlers with awaited httpx.AsyncClient (context-managed close); run Whisper transcription and engine.list_models via asyncio.to_thread so they don't block the event loop; and harden TelemetryStore/aggregator SQLite for concurrency (WAL, synchronous=NORMAL, busy_timeout=5000, plus a write-serializing lock on the shared connection). Adds async-usage assertions and a real 8-thread concurrent-write test. Related: #570 (async httpx, different issue #559).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@SoulSniper-V2

Copy link
Copy Markdown
Contributor Author

Hey @ElliotSlusky @selena731! Now that #597 is merged, PR #570 is ready for review to close sub-issue #559. It refactors the remaining sync httpx handlers to AsyncClient and includes dedicated non-blocking regression tests. Fully rebased against main and all tests are passing clean!

@selena731 selena731 added the pr:needs review Action required by maintainer (comment, review, merge, close) label Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:needs review Action required by maintainer (comment, review, merge, close)

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Fix: use httpx.AsyncClient in async FastAPI handlers to prevent event loop blocking

3 participants