fix(chat): guard activity poll against overlapping interval ticks

This commit is contained in:
matevip 2026-05-17 09:58:55 +08:00
parent 6d70834227
commit 146be51442

View File

@ -1150,6 +1150,12 @@ function applyPendingRouteAction() {
// ChatConsole WeChat/DingTalk/
// F5 /
let activityPollTimer: number | null = null
// Reentrancy guard: setInterval fires every ACTIVITY_POLL_MS regardless of
// whether the previous async pollActivity has finished. If one cycle runs long
// (slow reconnectStream / sluggish backend), unguarded ticks stack up and run
// concurrently, multiplying in-flight requests. This flag keeps one cycle at a
// time late ticks become no-ops until the running cycle returns.
let activityPolling = false
const ACTIVITY_POLL_MS = 4000
// Cron progress placeholder: when a cron job is mid-run on the currently
@ -1223,39 +1229,46 @@ function hasLocalOnlyFailedTail(): boolean {
async function pollActivity() {
//
if (typeof document !== 'undefined' && document.hidden) return
// Skip when the previous cycle is still running so slow polls can't stack.
if (activityPolling) return
activityPolling = true
try {
await loadConversations()
} catch {
//
}
// +
if (currentConversationId.value && !isGenerating.value && streamPhase.value !== 'awaiting_approval') {
const cid = currentConversationId.value
try {
const statusRes: any = await conversationApi.getStatus(cid)
if (currentConversationId.value !== cid) return
const running = statusRes?.data?.streamStatus === 'running'
if (running) {
//
// 1. DB user ""
// assistant content_delta
// 2. content_delta assistant
await refreshCurrentConversationMessages(cid)
if (currentConversationId.value !== cid || isGenerating.value) return
await reconnectStream(cid)
} else if (!hasLocalOnlyFailedTail()) {
// DB user / assistant
// SSE setup
// user/ assistant DB
// toast
await refreshCurrentConversationMessages(cid)
}
await loadConversations()
} catch {
//
//
}
// Cron progress placeholder independent of streamStatus because cron
// runs use the non-streaming chat() path, so streamStatus stays idle.
await refreshActiveCronRuns(cid)
// +
if (currentConversationId.value && !isGenerating.value && streamPhase.value !== 'awaiting_approval') {
const cid = currentConversationId.value
try {
const statusRes: any = await conversationApi.getStatus(cid)
if (currentConversationId.value !== cid) return
const running = statusRes?.data?.streamStatus === 'running'
if (running) {
//
// 1. DB user ""
// assistant content_delta
// 2. content_delta assistant
await refreshCurrentConversationMessages(cid)
if (currentConversationId.value !== cid || isGenerating.value) return
await reconnectStream(cid)
} else if (!hasLocalOnlyFailedTail()) {
// DB user / assistant
// SSE setup
// user/ assistant DB
// toast
await refreshCurrentConversationMessages(cid)
}
} catch {
//
}
// Cron progress placeholder independent of streamStatus because cron
// runs use the non-streaming chat() path, so streamStatus stays idle.
await refreshActiveCronRuns(cid)
}
} finally {
activityPolling = false
}
}