Skip to content

chat message loading race conditions, log disorder and infinite loops #30259

@lslzl3000

Description

@lslzl3000

Self Checks

  • I have read the Contributing Guide and Language Policy.
  • This is only for bug report, if you would like to ask a question, please head to Discussions.
  • I have searched for existing issues search for existing issues, including closed ones.
  • I confirm that I am using English to submit this report, otherwise it will be closed.
  • 【中文用户 & Non English User】请使用英语提交,否则会被关闭 :)
  • Please do not modify this template :) and fill in all the required fields.

Dify version

1.11.2

Cloud or Self Hosted

Self Hosted (Docker)

Steps to reproduce

Problem Description

When viewing chat message logs in the console with slow network conditions, the following bugs occur:

Issues

  1. Message Loading Order Chaos: Messages appear in incorrect chronological order when scrolling to load more history
  2. Infinite Loading Loop: Scrolling triggers continuous API requests, sometimes loading duplicate messages
  3. Race Conditions: Multiple concurrent requests caused by rapid scrolling lead to data inconsistency

Root Causes

After analyzing web/app/components/app/log/list.tsx, I identified several critical issues:

1. Race Condition in Loading State

const isLoadingRef = useRef(false)

const fetchData = useCallback(async () => {
  if (isLoadingRef.current)  // ❌ Not preventing concurrent requests effectively
    return
    
  try {
    isLoadingRef.current = true
    // ... async network request
  } finally {
    isLoadingRef.current = false
  }
}, [allChatItems, ...])

Problem: With slow networks and fast scrolling:

  • First request is still pending
  • User scrolls again before isLoadingRef.current is checked
  • Second request fires with outdated allChatItems state
  • Responses arrive in unpredictable order → message chaos

2. Incorrect Pagination Anchor Selection

const answerItems = allChatItems.filter(item => item.isAnswer)
const oldestAnswerItem = answerItems[answerItems.length - 1]  // ❌ May select wrong item during concurrent updates
if (oldestAnswerItem?.id)
  params.first_id = oldestAnswerItem.id

Problem:

  • When first request hasn't returned, allChatItems contains stale data
  • Second request uses same first_id, fetching duplicate data
  • Or after first request updates allChatItems, pagination skips messages

3. Multiple Conflicting Scroll Listeners

The code has TWO separate scroll listeners that can trigger simultaneously:

// Listener 1: Uses loadMoreMessages (around line 505-592)
useEffect(() => {
  const handleScroll = () => {
    if (isNearTop && hasMore && !isLoading) {
      loadMoreMessages()  // ❌ First loading function
    }
  }
  scrollContainer.addEventListener('scroll', handleScroll)
}, [hasMore, isLoading, loadMoreMessages])

// Listener 2: Uses fetchData (around line 617-656)
useEffect(() => {
  const handleScroll = () => {
    if (scrollTop < SCROLL_THRESHOLD_PX && hasMore && !isLoadingRef.current) {
      fetchData()  // ❌ Second loading function!
    }
  }
  scrollableDiv.addEventListener('scroll', handleScroll)
}, [threadChatItems.length, hasMore, fetchData])

Problem: Both listeners can fire simultaneously, causing duplicate API requests

4. Incomplete Throttle Mechanism

let lastLoadTime = 0  // ❌ Local variable, resets on every effect recreation
const throttleDelay = 200

const handleScroll = () => {
  const now = Date.now()
  if (isNearTop && hasMore && !isLoading && (now - lastLoadTime > throttleDelay)) {
    lastLoadTime = now
    loadMoreMessages()
  }
}

Problem: lastLoadTime is a local variable inside useEffect, so it resets whenever the effect re-runs

5. Retry Logic Causing Infinite Loops

// In loadMoreMessages function (around line 446-503)
const existingIds = new Set(allChatItems.map(item => item.id))
const uniqueNewItems = newItems.filter(item => !existingIds.has(item.id))

if (uniqueNewItems.length === 0) {
  // Retry with next item, but can cause infinite loops! ❌
  if (allChatItems.length > 1) {
    const nextId = allChatItems[1].id.replace('question-', '').replace('answer-', '')
    const retryParams = { ...params, first_id: nextId }
    const retryRes = await fetchChatMessages(...)
    // ... more retry logic
  }
}

Problem: This retry logic can trigger endless requests if server consistently returns duplicates

✔️ Expected Behavior

viewing chat message logs in the console with slow network should be working right

❌ Actual Behavior

  1. Message Loading Order Chaos: Messages appear in incorrect chronological order when scrolling to load more history
  2. Infinite Loading Loop: Scrolling triggers continuous API requests, sometimes loading duplicate messages
  3. Race Conditions: Multiple concurrent requests caused by rapid scrolling lead to data inconsistency

Metadata

Metadata

Assignees

No one assigned

    Labels

    🐞 bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions