Repository navigation
fix(community): build the remaining search clients off the event loop - #7933
Conversation
willem-bd
left a comment
There was a problem hiding this comment.
Verified against head cac93cc: all three remaining on-loop construction sites named in backend/AGENTS.md now build their clients via asyncio.to_thread (jsonify _read_rows, SearXNG _search_page, Jina _admission_client plus the new _plain_client), requests and cleanup stay on the loop, and I found no other on-loop httpx.AsyncClient construction in the three provider dirs. The AGENTS.md enumeration now covers every converted provider including Crawl4AI (the follow-up #7906 asked for), and narrowing the task-owned-cancellation claim to Browserless only matches what the code actually implements. Tests follow the blocking-io convention and cover both Jina paths (plain and admitted). The one thing worth resolving before merge is the overlap with #7932 on the jsonify site, noted inline.
| version: int | None = None | ||
| truncated = False | ||
| async with httpx.AsyncClient(timeout=settings.timeout) as client: | ||
| async with await asyncio.to_thread(httpx.AsyncClient, timeout=settings.timeout) as client: |
There was a problem hiding this comment.
Heads-up: open PR #7932 fixes this exact site with a stronger construction pattern — task-owned asyncio.create_task(asyncio.to_thread(...)) + asyncio.shield, which closes the client if the caller is cancelled during setup — plus its own backend/tests/blocking_io/test_jsonify_client_ssl.py and an overlapping AGENTS.md edit. Before this change, construction was synchronous on the loop, so there was no cancellation window here; plain await asyncio.to_thread(...) introduces one: if the awaiting task is cancelled while the worker thread is still building the client, the client gets constructed and its aclose() never runs (no sockets are open yet, so the impact is small, but it is exactly the window #7932's task ownership closes — the same gap shared by the InfoQuest/You.com/GroundRoute/Webz/Crawl4AI sites). The two PRs are functionally compatible, but they conflict on this line and in AGENTS.md, and both add a jsonify TLS test under backend/tests/blocking_io/. Worth coordinating with @Doy369 — ideally adopting the cancellation-safe _open_client shape here so the sweep lands the stronger pattern for all three providers, or letting #7932 rebase onto this.
There was a problem hiding this comment.
Thanks for flagging the overlap. Would it work to keep the Jsonify fix in #7932 and scope #7933 to SearXNG and Jina? That would let both PRs move forward independently while keeping Jsonify's cancellation cleanup and regression tests. If you'd prefer #7933 to land first, I can rebase #7932 on top of it.
There was a problem hiding this comment.
@BlueX888, following up on the overlap now that #7932 is updated to 5cdf0d6, rebased onto current main, and includes the cancellation-diagnostic fix from the latest review. Could you confirm this split?
- fix(jsonify): keep HTTPX TLS setup off the event loop #7932: Jsonify construction, cancellation cleanup and diagnostics, and its regression tests.
- fix(community): build the remaining search clients off the event loop #7933: SearXNG and both Jina client paths, with their regression tests.
Suggested order: land #7932 first, then rebase #7933. That would mean dropping the Jsonify tools.py hunk and the jsonify case/branches in test_remaining_search_clients_build_tls_off_loop. The AGENTS.md update can then build on the merged paragraph, retaining the Browserless/Jsonify cancellation guarantee while adding SearXNG and Jina to the off-loop list.
The shared-client helper will be a separate follow-up after these fixes land, initially covering Browserless and Jsonify. Please let me know if a different merge order works better for you and the maintainers.
There was a problem hiding this comment.
Confirmed — #7932 keeps the Jsonify construction, cancellation cleanup and its regression tests; #7933 drops the Jsonify hunk and the jsonify case from test_remaining_search_clients_build_tls_off_loop, keeping SearXNG plus both Jina client paths. Landing #7932 first and then rebasing #7933 works for me. I will rebuild the backend/AGENTS.md paragraph on top of #7932's version so the Browserless/Jsonify task-owned guarantee stays intact and SearXNG/Jina are added to the off-loop list. Thanks for the clean split.
| @@ -137,12 +137,11 @@ Read the nearest scoped `AGENTS.md` for subsystem ownership. | |||
| Read-only knowledge: [contract](docs/KNOWLEDGE.md) covers independent retrieval | |||
| tools, scope intersection and extension-owned providers. | |||
| HTTPX clients for several community providers, including RAGFlow, LightRAG, | |||
There was a problem hiding this comment.
This enumeration is accurate at the head — I verified to_thread construction for RAGFlow, LightRAG, You.com, GroundRoute, Webz, Crawl4AI, Browserless, InfoQuest, jsonify, SearXNG and Jina, and Crawl4AI's presence resolves the #7906 follow-up; keeping the leading 'several community providers, including' preserves non-exhaustiveness. For completeness if this PR closes out the series: one on-loop httpx.AsyncClient construction remains in the community package outside this sentence's scope — aio_sandbox/backend.py:128 (wait_for_sandbox_ready_async) builds its client directly on the loop from async runtime paths. Sandbox infra rather than a search provider (and usually plain-HTTP local URLs), so fine to leave out of this change, but it would be the natural last follow-up.
|
@BlueX888 please resolve the conflict with the main branch. |
cac93cc to
d2d4dfb
Compare
|
Rebased onto main |
What changed
Follow-up to #7899, which moved the InfoQuest clients off the event loop after @willem-bd's review noted that the
backend/AGENTS.mdprovider list still named jsonify, SearXNG and Jina as constructing their clients on the loop. This covers those three remaining sites:jsonify/tools.py: build the client viaasyncio.to_threadin_read_rows.searxng/searxng_client.py: same in_search_page.jina_ai/jina_client.py: same for the plain client (new_plain_clientcontext manager) and the admission client; requests and cleanup stay on the loop.httpx.AsyncClient(...)resolves the system trust store synchronously, so constructing it on the event loop blocks every task. The construction now runs in the default executor; nothing else about the request path changes.backend/AGENTS.mdis updated in the same change: all community HTTP clients now useasyncio.to_thread, the remaining-provider sentence is gone, and only Browserless keeps the task-owned cancellation claim (which its_open_clientactually implements).Tests
jsonify/searxng/jinacase tobackend/tests/blocking_io/test_search_client_ssl.py: assertsssl.create_default_contextruns off the loop thread,sendruns on it, one client is built and closed.test_admitted_client_builds_tls_off_looptobackend/tests/test_jina_request_admission.pyfor the admission client path, using the file's existing policy isolation fixture.38c0d43's production diff is byte-identical after the rebase onto097fa6d; base-red is 3/3 providers failing withTLS trust loading blocks the event loop, and the admission test also fails on base.Notes
backend/AGENTS.md.