fix: rotate the LinkContentFetcher user agent per fetch, not per component - #12357
fix: rotate the LinkContentFetcher user agent per fetch, not per component#12357pcbeingused333 wants to merge 1 commit into
Conversation
…onent `run()` fetches URLs concurrently through a ThreadPoolExecutor and `run_async()` gathers them, but the rotation cursor lived on the component as `current_user_agent_idx`. Every in-flight request read and wrote the same counter: a retry triggered by one URL rotated the user agent for all the others, and each fetch that finished reset the counter to 0 underneath the requests still running. With several URLs retrying at once, the retries mostly went out with the un-rotated user agent — the feature silently did not do what it documents. Give each fetch its own cursor: a local in `_get_response` that the tenacity `after` callback advances, and a local in `_get_response_async`. `_get_headers` now takes the user agent for the attempt instead of reading component state. `current_user_agent_idx` and `_switch_user_agent` were that shared state and are gone. Neither is part of the documented API, and neither survives serialization. Fixes deepset-ai#12287 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Someone is attempting to deploy a commit to the deepset Team on Vercel. A member of the Team first needs to authorize it. |
|
Alex Castillo seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
|
Hi @pcbeingused333, thanks for your interest in contributing to Haystack! 🙏 This is an automated message to help us keep the review queue healthy. |
|
Thanks for the flag — the guard is right and I missed it. I checked #12289 by its title, which describes adding a component, and did not read the body, where it is the same fix for the same race in the same file. Closing this as the duplicate; #12289 got there first. Leaving one note in case it is useful to whoever reviews #12289: the async path has the same shared-cursor problem ( |
Related Issues
Proposed Changes:
The rotation cursor lived on the component (
self.current_user_agent_idx), butrun()fetches the URLs through aThreadPoolExecutorandrun_async()gathers them. Every in-flight request read and wrote that one counter, so with several URLs in the same call:0(in thefinallyof_fetch/_fetch_async) underneath the requests still running.The retries then mostly went out with the un-rotated user agent, which is the behaviour reported in #12287. A single-URL call rotates correctly, which is why this went unnoticed.
Each fetch now keeps its own cursor: a local in
_get_responsethat the tenacityaftercallback advances, and a local in_get_response_async._get_headerstakes the user agent for the attempt instead of reading component state.current_user_agent_idxand_switch_user_agentwere that shared state and are removed. Neither is part of the documented API and neither is serialized byto_dict, so nothing that survives a pipeline round-trip changes.How did you test it?
test_user_agent_rotation_is_independent_per_url: fetches 8 URLs in onerun()call with 4 user agents, each URL failing once. Every URL must senduser_agents[1]on its successful retry. Onmainthey mostly senduser_agents[0]; with this change every URL sendsuser_agents[1].hatch run test:unit— 6116 passed, 10 skipped.hatch run test:typesandhatch run fmtclean.Notes for the reviewer
The behaviour pinned by the existing tests is unchanged: the first attempt still uses
user_agents[0]and the first retry still usesuser_agents[1].The one judgement call is deleting
current_user_agent_idxrather than leaving it as a vestigial attribute. It was only ever the shared cursor, and keeping it would leave an attribute that no longer describes anything. Happy to keep it (unused) if you would rather not touch the surface at all.Checklist
I used an AI assistant while writing this change. I have reviewed it, reproduced the bug, and run the tests.