This is an automated email from the ASF dual-hosted git repository.
lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git
The following commit(s) were added to refs/heads/rocketmq-studio by this push:
new 1a2affb3c fix(ai): keep stale stream cleanup off the active send guard
(#4861)
1a2affb3c is described below
commit 1a2affb3c473ce4d2a24ac5a85cf8dc2dda68eb6
Author: Hanabi <[email protected]>
AuthorDate: Thu Sep 24 18:19:09 2026 +0800
fix(ai): keep stale stream cleanup off the active send guard (#4861)
fix(ai): keep stale stream cleanup off the active send guard
---
web/src/pages/ai/hooks/useAgentRun.test.ts | 27 +++++++++++++++++++++++++++
web/src/pages/ai/hooks/useAgentRun.ts | 4 ++--
2 files changed, 29 insertions(+), 2 deletions(-)
diff --git a/web/src/pages/ai/hooks/useAgentRun.test.ts
b/web/src/pages/ai/hooks/useAgentRun.test.ts
index 768b05197..3e49bc4d0 100644
--- a/web/src/pages/ai/hooks/useAgentRun.test.ts
+++ b/web/src/pages/ai/hooks/useAgentRun.test.ts
@@ -494,4 +494,31 @@ describe('useAgentRun', () => {
expect(result.current.error).toBe('');
expect(onError).not.toHaveBeenCalled();
});
+
+ it('keepsTheNewStreamGuardWhenAnAbortedStreamSettlesTest', async () => {
+ const { result, rerender } = render();
+ let firstSend!: Promise<void>;
+ await act(async () => {
+ firstSend = result.current.send(7, { message: 'first conversation' });
+ });
+
+ await act(async () => {
+ rerender({ id: 8 });
+ });
+ expect(openedStreams[0].signal.aborted).toBe(true);
+
+ await act(async () => {
+ void result.current.send(8, { message: 'new conversation' });
+ });
+ await act(async () => {
+ openedStreams[0].fail(new DOMException('Aborted', 'AbortError'));
+ await firstSend;
+ });
+
+ await act(async () => {
+ void result.current.send(8, { message: 'duplicate send' });
+ });
+ expect(openedStreams).toHaveLength(2);
+ expect(result.current.isStreaming).toBe(true);
+ });
});
diff --git a/web/src/pages/ai/hooks/useAgentRun.ts
b/web/src/pages/ai/hooks/useAgentRun.ts
index 03f0c1a57..cf7f466de 100644
--- a/web/src/pages/ai/hooks/useAgentRun.ts
+++ b/web/src/pages/ai/hooks/useAgentRun.ts
@@ -251,12 +251,12 @@ export function useAgentRun(
const finishStream = useCallback(
async (requestId: number, controller: AbortController, streamFailure:
unknown | null) => {
if (abortControllerRef.current === controller)
abortControllerRef.current = null;
+ // A newer stream (or a navigation) owns the guard and UI now.
+ if (requestId !== streamRequestIdRef.current) return;
// Released before the refetch is awaited on purpose: a reload that
hangs must not leave the
// composer unable to send. A send that overtakes this finally block
bumps the request id, and
// the guard after the await below then keeps this run's cleanup off the
newer one's state.
chatInFlightRef.current = false;
- // A newer stream (or a navigation) owns the UI now; touching state here
would clobber it.
- if (requestId !== streamRequestIdRef.current) return;
setIsStreaming(false);
// The stream is over, so there is nothing left to wait for whichever
way it ended: clearing