Skip to content

fix(community): build the remaining search clients off the event loop - #7933

Merged
WillemJiang merged 2 commits into
bytedance:mainfrom
BlueX888:contrib/async-tls-offloop-sweep
Nov 9, 2026
Merged

WillemJiang merged 2 commits into
bytedance:mainfrom
BlueX888:contrib/async-tls-offloop-sweep

Conversation

@BlueX888

@BlueX888 BlueX888 commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

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.md provider 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 via asyncio.to_thread in _read_rows.
  • searxng/searxng_client.py: same in _search_page.
  • jina_ai/jina_client.py: same for the plain client (new _plain_client context 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.md is updated in the same change: all community HTTP clients now use asyncio.to_thread, the remaining-provider sentence is gone, and only Browserless keeps the task-owned cancellation claim (which its _open_client actually implements).

Tests

  • Added a parametrized jsonify/searxng/jina case to backend/tests/blocking_io/test_search_client_ssl.py: asserts ssl.create_default_context runs off the loop thread, send runs on it, one client is built and closed.
  • Added test_admitted_client_builds_tls_off_loop to backend/tests/test_jina_request_admission.py for the admission client path, using the file's existing policy isolation fixture.
  • Base 38c0d43's production diff is byte-identical after the rebase onto 097fa6d; base-red is 3/3 providers failing with TLS trust loading blocks the event loop, and the admission test also fails on base.
  • Candidate: 385 passed across the blocking-I/O, jsonify, SearXNG and Jina suites (including the 4 InfoQuest tests from fix(community): initialize InfoQuest TLS clients off the event loop #7899); ruff check/format clean. The AGENTS chain-budget checker reports 0 errors.

Notes

@github-actions github-actions Bot added area:docs Documentation and Markdown only risk:medium Medium risk: regular code changes size/M PR changes 100-300 lines labels Nov 6, 2026

@willem-bd willem-bd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@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?

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Comment thread backend/AGENTS.md
@@ -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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

@WillemJiang

Copy link
Copy Markdown
Collaborator

@BlueX888 please resolve the conflict with the main branch.

@BlueX888
BlueX888 force-pushed the contrib/async-tls-offloop-sweep branch from cac93cc to d2d4dfb Compare November 7, 2026 08:36
@BlueX888

BlueX888 commented Nov 7, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto main d0007090c. The jsonify change and its tests now come from #7932, so this PR keeps only SearXNG and Jina, and backend/AGENTS.md lists all three. Provider-related tests pass (535 passed, including the blocking-I/O TLS tests), and ruff format/check are clean. CI is running on the new head d2d4dfb07.

@github-actions github-actions Bot added size/S PR changes 20-100 lines and removed size/M PR changes 100-300 lines labels Nov 7, 2026
@WillemJiang
WillemJiang merged commit aa1f376 into bytedance:main Nov 9, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:docs Documentation and Markdown only risk:medium Medium risk: regular code changes size/S PR changes 20-100 lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants