Skip to content

fix: stop webchat stream from crashing on queue polling errors#6123

Open
stablegenius49 wants to merge 1 commit intoAstrBotDevs:masterfrom
stablegenius49:pr-factory/issue-6118-webchat-stream
Open

fix: stop webchat stream from crashing on queue polling errors#6123
stablegenius49 wants to merge 1 commit intoAstrBotDevs:masterfrom
stablegenius49:pr-factory/issue-6118-webchat-stream

Conversation

@stablegenius49
Copy link
Contributor

@stablegenius49 stablegenius49 commented Mar 12, 2026

Modifications / 改动点

  • Fix the webchat SSE loop in astrbot/dashboard/routes/chat.py so queue polling errors do not fall through to if not result with an unbound local.

  • Treat CancelledError as a clean client disconnect (break) and keep generic queue polling errors non-fatal (continue).

  • Add regression tests for the new polling helper to cover success, generic exceptions, and CancelledError.

  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果

python3.11 -m pytest tests/test_chat_route.py -q
...                                                                      [100%]
3 passed, 3 warnings in 1.36s

python3 -m compileall astrbot/dashboard/routes/chat.py
Compiling 'astrbot/dashboard/routes/chat.py'...

Closes #6118.


Checklist / 检查清单

  • 😊 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。/ If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
  • 👀 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。/ My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
  • 🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了 requirements.txtpyproject.toml 文件相应位置。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
  • 😮 我的更改没有引入恶意代码。/ My changes do not introduce malicious code.

Summary by Sourcery

更稳健地处理网页聊天流式队列轮询错误,防止 SSE 崩溃,并更干净地处理客户端断开连接。

Bug Fixes:

  • 当队列轮询抛出异常或返回空结果时,防止网页聊天 SSE 流式传输崩溃。

Tests:

  • 为网页聊天轮询辅助工具添加异步单元测试,覆盖正常队列负载、通用轮询异常以及 CancelledError 处理。
Original summary in English

Summary by Sourcery

Handle webchat streaming queue polling errors more robustly to prevent SSE crashes and treat client disconnects cleanly.

Bug Fixes:

  • Prevent webchat SSE streaming from crashing when queue polling raises exceptions or returns no result.

Tests:

  • Add async unit tests covering normal queue payloads, generic polling exceptions, and CancelledError handling in the webchat polling helper.

@auto-assign auto-assign bot requested review from Soulter and advent259141 March 12, 2026 11:46
@dosubot dosubot bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Mar 12, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the robustness of the webchat SSE stream by refactoring its error handling mechanism. It introduces a dedicated helper function to gracefully manage various queue polling exceptions, preventing stream crashes and ensuring a more stable user experience, particularly during client disconnections or transient errors.

Highlights

  • Webchat Stream Stability: Fixed an issue in the webchat Server-Sent Events (SSE) loop where queue polling errors could lead to crashes due to unbound local variables.
  • Error Handling Refinement: Introduced a new helper function, _poll_webchat_stream_result, to centralize and improve error handling for queue polling. CancelledError is now treated as a clean client disconnect, while other generic exceptions are handled as non-fatal, allowing the stream to continue.
  • Test Coverage: Added new regression tests for the _poll_webchat_stream_result helper to ensure correct behavior for successful polling, generic exceptions, and CancelledError.
Changelog
  • astrbot/dashboard/routes/chat.py
    • Introduced a new asynchronous helper function _poll_webchat_stream_result to encapsulate queue polling logic and error handling.
    • Refactored the stream function to utilize the new _poll_webchat_stream_result helper, simplifying its error management.
  • tests/test_chat_route.py
    • Added new test cases for _poll_webchat_stream_result to cover scenarios like CancelledError, generic exceptions, and successful payload retrieval.
Activity
  • The author stablegenius49 created this pull request to address a webchat stream crashing issue.
  • The pull request includes a detailed description outlining modifications, test results, and a comprehensive checklist.
  • The checklist confirms that the changes are not breaking, have been well-tested, introduce no new dependencies, and contain no malicious code.
  • This pull request closes issue [Bug] #6118.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - 我发现了 1 个问题,并给出了一些整体性的反馈:

  • 建议为 _poll_webchat_stream_result 添加简短的文档字符串(docstring)和类型注解,把 (result, should_break) 的返回约定,以及预期的队列接口为后续维护者明确说明。
  • 由于 _poll_webchat_stream_result 现在是流式循环行为中的关键部分,你可能还想把 if not result: continue 这段逻辑也集中到这个 helper 里,这样调用方只需要根据 should_break 做分支,而由 helper 完整负责轮询/继续的语义。
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider adding a short docstring and type hints to `_poll_webchat_stream_result` to make the `(result, should_break)` return contract and expected queue interface explicit for future maintainers.
- Since `_poll_webchat_stream_result` is now a key part of the streaming loop behavior, you might want to centralize the `if not result: continue` logic into the helper as well, so the caller only branches on `should_break` and the helper fully owns the polling/continue semantics.

## Individual Comments

### Comment 1
<location path="tests/test_chat_route.py" line_range="24-25" />
<code_context>
+        return self._result
+
+
+@pytest.mark.asyncio
+async def test_poll_webchat_stream_result_breaks_on_cancelled_error():
+    result, should_break = await _poll_webchat_stream_result(
+        _QueueThatRaises(asyncio.CancelledError()),
</code_context>
<issue_to_address>
**suggestion (testing):** Add a test case for the timeout (`asyncio.TimeoutError`) branch of `_poll_webchat_stream_result`.

This path has distinct behavior for `asyncio.TimeoutError` (returning `(None, False)` so the caller continues looping), but we currently only test success, generic exceptions, and `CancelledError`. Please add a test that simulates a timeout and asserts `result is None` and `should_break is False`. You can keep it fast by using a queue stub whose `get()` raises `asyncio.TimeoutError` directly, similar to `_QueueThatRaises`.
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得我们的评审有帮助,欢迎分享 ✨
帮我变得更有用!请对每条评论点 👍 或 👎,我会根据你的反馈来改进评审质量。
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • Consider adding a short docstring and type hints to _poll_webchat_stream_result to make the (result, should_break) return contract and expected queue interface explicit for future maintainers.
  • Since _poll_webchat_stream_result is now a key part of the streaming loop behavior, you might want to centralize the if not result: continue logic into the helper as well, so the caller only branches on should_break and the helper fully owns the polling/continue semantics.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider adding a short docstring and type hints to `_poll_webchat_stream_result` to make the `(result, should_break)` return contract and expected queue interface explicit for future maintainers.
- Since `_poll_webchat_stream_result` is now a key part of the streaming loop behavior, you might want to centralize the `if not result: continue` logic into the helper as well, so the caller only branches on `should_break` and the helper fully owns the polling/continue semantics.

## Individual Comments

### Comment 1
<location path="tests/test_chat_route.py" line_range="24-25" />
<code_context>
+        return self._result
+
+
+@pytest.mark.asyncio
+async def test_poll_webchat_stream_result_breaks_on_cancelled_error():
+    result, should_break = await _poll_webchat_stream_result(
+        _QueueThatRaises(asyncio.CancelledError()),
</code_context>
<issue_to_address>
**suggestion (testing):** Add a test case for the timeout (`asyncio.TimeoutError`) branch of `_poll_webchat_stream_result`.

This path has distinct behavior for `asyncio.TimeoutError` (returning `(None, False)` so the caller continues looping), but we currently only test success, generic exceptions, and `CancelledError`. Please add a test that simulates a timeout and asserts `result is None` and `should_break is False`. You can keep it fast by using a queue stub whose `get()` raises `asyncio.TimeoutError` directly, similar to `_QueueThatRaises`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +24 to +25
@pytest.mark.asyncio
async def test_poll_webchat_stream_result_breaks_on_cancelled_error():
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (testing):_poll_webchat_stream_result 的超时(asyncio.TimeoutError)分支添加一个测试用例。

这条路径对 asyncio.TimeoutError 有不同的行为(返回 (None, False),使调用方继续循环),但目前我们只测试了成功情况、通用异常和 CancelledError。请添加一个模拟超时的测试,并断言 result is Noneshould_break is False。你可以像 _QueueThatRaises 一样,使用一个其 get() 会直接抛出 asyncio.TimeoutError 的队列 stub,从而保持测试运行速度较快。

Original comment in English

suggestion (testing): Add a test case for the timeout (asyncio.TimeoutError) branch of _poll_webchat_stream_result.

This path has distinct behavior for asyncio.TimeoutError (returning (None, False) so the caller continues looping), but we currently only test success, generic exceptions, and CancelledError. Please add a test that simulates a timeout and asserts result is None and should_break is False. You can keep it fast by using a queue stub whose get() raises asyncio.TimeoutError directly, similar to _QueueThatRaises.

@dosubot dosubot bot added the feature:chatui The bug / feature is about astrbot's chatui, webchat label Mar 12, 2026
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request effectively addresses a critical issue where webchat stream crashes due to queue polling errors by refactoring the polling logic into a dedicated helper function, _poll_webchat_stream_result. This significantly improves the robustness and maintainability of the streaming mechanism. The addition of comprehensive unit tests for the new helper function is also a great step towards ensuring its reliability. The changes correctly differentiate between CancelledError (clean disconnect) and generic exceptions, preventing stream crashes and improving error handling.

Comment on lines +39 to +50
async def _poll_webchat_stream_result(back_queue, username: str):
try:
result = await asyncio.wait_for(back_queue.get(), timeout=1)
except asyncio.TimeoutError:
return None, False
except asyncio.CancelledError:
logger.debug(f"[WebChat] 用户 {username} 断开聊天长连接。")
return None, True
except Exception as e:
logger.error(f"WebChat stream error: {e}")
return None, False
return result, False
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The new _poll_webchat_stream_result function is a great addition for encapsulating the polling logic and error handling. To further enhance maintainability and clarity, consider adding a docstring to explain its purpose, parameters, and return values. Additionally, specifying the type hint for back_queue as asyncio.Queue would improve type checking and readability.

async def _poll_webchat_stream_result(back_queue: asyncio.Queue, username: str):
    """Polls the back_queue for results, handling timeouts, cancellations, and other exceptions.

    Args:
        back_queue: The asyncio.Queue to poll for results.
        username: The username associated with the webchat stream.

    Returns:
        A tuple containing the result (or None) and a boolean indicating if the stream should break.
    """
    try:
        result = await asyncio.wait_for(back_queue.get(), timeout=1)
    except asyncio.TimeoutError:
        return None, False
    except asyncio.CancelledError:
        logger.debug(f"[WebChat] 用户 {username} 断开聊天长连接。")
        return None, True
    except Exception as e:
        logger.error(f"WebChat stream error: {e}")
        return None, False
    return result, False

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature:chatui The bug / feature is about astrbot's chatui, webchat size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]

2 participants