fix(ragflow): preserve final retry response body - #2631
Conversation
|
CI 更新 / CI update: Ubuntu 构建失败发生在与本 PR 无关的 本 PR 仅修改 RAGFlow client 及其回归测试;RAGFlow 模块 97/97、Spotless 88 个模块均已在本地通过。我尝试重跑 workflow,但外部贡献者没有该仓库的 Actions 重跑权限。烦请维护者协助 rerun CI。 The Ubuntu job failed during JUnit |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
CI 更新:在不修改代码和提交 SHA( 因此,首轮 CI update: a new workflow was triggered without changing the code or commit SHA ( This confirms that the initial |
There was a problem hiding this comment.
Pull request overview
This PR fixes RAGFlow retry handling so the final response (including terminal 5xx responses) remains open for the caller to read the body, preventing IllegalStateException: closed during response.body().string() in retrieve().
Changes:
- Adjust
RetryInterceptorresponse-closing logic to avoid closing the response that will be returned to the caller (including final 5xx). - Add a regression test to ensure the final error response body is readable after a retry and that the request count matches expectations.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-ragflow/src/main/java/io/agentscope/core/rag/integration/ragflow/RAGFlowClient.java | Updates retry interceptor to preserve the final response for the caller instead of closing it in finally. |
| agentscope-extensions/agentscope-extensions-rag/agentscope-extensions-rag-ragflow/src/test/java/io/agentscope/core/rag/integration/ragflow/RAGFlowClientTest.java | Adds a regression test validating final 500 response body readability after a retry and verifying request count. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| @@ -365,10 +368,12 @@ public Response intercept(Chain chain) throws IOException { | |||
| throw lastException; | |||
| } | |||
|
|
|||
| responseReturned = true; | |||
|
已根据 review 中指出的重试边界补充修复(fcabde80)。 问题场景是:前一次尝试抛出 IOException,但最后一次尝试正常获得 HTTP 5xx 响应。原实现会在循环结束后再次检查历史 lastException,导致旧的传输异常覆盖最终 HTTP 响应,同时使调用方无法读取最终响应体。 本次采用最小修改:删除 lastException 的声明、赋值及循环后的抛出逻辑。最终一次请求如果仍发生 IOException,现有 catch 分支会直接抛出;如果最终获得 HTTP 响应(包括 5xx),则将该响应返回给上层读取并按状态码处理。 同时新增回归测试 testRetryReturnsFinalErrorResponseAfterEarlierIOException,确定性模拟 IOException -> 500,验证最终响应及响应体能够返回,并且响应不会被拦截器提前关闭。 验证结果:
|
AgentScopeJavaBot
left a comment
There was a problem hiding this comment.
🤖 AI Review
This PR fixes issue #714 where RetryInterceptor was closing the final retry response before returning it to the caller, making the response body unreadable. The fix introduces a responseReturned ownership flag to distinguish responses handed to the caller (kept open) from responses discarded internally (closed in finally). Additionally, response = null is set after closing to prevent double-close scenarios. All logic paths verified correct.
AgentScopeJavaBot
left a comment
There was a problem hiding this comment.
🤖 AI Review
This PR fixes issue #714 where RetryInterceptor was closing the final retry response before returning it to the caller, making the response body unreadable. The fix introduces a responseReturned ownership flag to distinguish responses handed to the caller (kept open) from responses discarded internally (closed in finally). Additionally, response = null is set after closing to prevent double-close scenarios. All logic paths verified correct.
| } | ||
| } | ||
|
|
||
| if (lastException != null) { |
There was a problem hiding this comment.
删除了这个异常会不会有问题?
我理解删除了就改变了这个拦截器的动作。
There was a problem hiding this comment.
是的,这里确实改变了一个分支,但改变的是历史异常覆盖最终请求结果的行为(之前bot提出的问题)。
lastException 只可能来自之前的重试;如果最后一次请求仍抛出 IOException,当前代码会在 attempt == maxRetries 的 catch 中直接 抛出异常,因此最终网络异常会正常向上传递。
删除后各终止路径为:
- 最终成功或 4xx:直接返回响应;
- 最终 5xx:返回最终响应,交由上层读取响应体并按状态码处理;
- 最终 IOException:直接抛出该次异常。
原本的逻辑的问题只发生在“前一次 IOException、最后一次得到 5xx”时:旧的 lastException 会覆盖最后一次 HTTP 响应。新增的 testRetryReturnsFinalErrorResponseAfterEarlierIOException 覆盖了这个场景。
所以这里不会放宽或吞掉最终异常,而是让最后一次尝试的结果决定最终行为。
There was a problem hiding this comment.
已补充测试提交 58bca961,生产代码未再修改。
新增 testRetryThrowsFinalIOExceptionAfterEarlierIOException,确定性模拟 IOException -> IOException,并验证:
- 最终向上传递的异常对象就是最后一次尝试抛出的
finalException; chain.proceed(request)恰好执行 2 次。
验证结果:聚焦测试 1/1、RAGFlowClientTest 39/39、RAGFlow 模块 99/99,Spotless 均通过。这条测试直接确认删除历史 lastException 后,最终传输异常仍会正常抛出;改变的仅是“历史 IOException 覆盖最终 HTTP 5xx 响应”的错误分支。
变更说明
RetryInterceptor在返回最终重试响应前将其关闭的问题根因与修复
原实现会在
finally中关闭所有非成功响应,包括已经准备返回给retrieve()的最终 5xx 响应。因此上层执行response.body().string()时会抛出IllegalStateException: closed。修复后,只有未交给调用方的响应会由拦截器关闭:重试过程中被替换的响应仍及时释放,而最终响应(包括 5xx)保持可读,并继续由调用方已有的 try-with-resources 负责关闭。
验证
RAGFlowClientTest#testRetryKeepsFinalErrorResponseBodyReadable:通过(修复前可稳定复现IllegalStateException: closed)mvn spotless:check:88 个模块全部通过mvn test:已执行;在agentscope-core的 2 个既有符号链接测试处因 Windows 当前进程缺少Files.createSymbolicLink所需特权而停止(此前已执行 2268 个测试,0 failures、2 errors、8 skipped),与本次 RAGFlow 改动无关Fixes #714