Skip to content

fix: Multiple memory leaks in CodeBlock component (CodeBlock_247, CodeBlock_459, CodeBlock_694) #4243

@kiwina

Description

@kiwina

name: Bug Report
description: Multiple potential memory leaks in CodeBlock component due to unmanaged async operations and timeouts.
labels: ["bug", "memory-leak", "auto-generated", "react", "async", "setTimeout"]
body:

  • type: markdown
    attributes:
    value: |
    Thanks for your report! Please check existing issues first:
    👉 https://github.com/RooCodeInc/Roo-Code/issues

  • type: input
    id: version
    attributes:
    label: App Version
    description: What version of Roo Code are you using? (e.g., v3.3.1)
    value: "Latest (auto-detected)"
    validations:
    required: true

  • type: dropdown
    id: provider
    attributes:
    label: API Provider
    options:
    - Anthropic
    - AWS Bedrock
    - Chutes AI
    - DeepSeek
    - Glama
    - Google Gemini
    - Google Vertex AI
    - Groq
    - Human Relay Provider
    - LiteLLM
    - LM Studio
    - Mistral AI
    - Ollama
    - OpenAI
    - OpenAI Compatible
    - OpenRouter
    - Requesty
    - Unbound
    - VS Code Language Model API
    - xAI (Grok)
    - Not Applicable / Other
    default_value: Not Applicable / Other
    validations:
    required: true

  • type: input
    id: model
    attributes:
    label: Model Used
    description: Exact model name (e.g., Claude 3.7 Sonnet). Use N/A if irrelevant.
    value: "N/A"
    validations:
    required: true

  • type: textarea
    id: steps
    attributes:
    label: 🔁 Steps to Reproduce
    description: |
    Help us see what you saw. Give clear, numbered steps:

    1. Setup (OS, extension version, settings)
    2. Exact actions (clicks, input, files, commands)
    3. What happened after each step
    
    Think like you're writing a recipe. Without this, we can't reproduce the issue.
    

    value: |
    1. Setup: Any OS, latest Roo Code extension version.
    2. Exact actions:
    a. Render a CodeBlock component (e.g., in a chat message).
    b. Cause the CodeBlock component to unmount while asynchronous operations are pending. This can occur in several scenarios:
    i. During syntax highlighting (useEffect hook at original line 247 in webview-ui/src/components/common/CodeBlock.tsx).
    ii. When highlightedCode changes, triggering a setTimeout to update button positions (useEffect hook at original line 456, setTimeout at original line 459).
    iii. When the collapse/expand button is clicked, triggering nested setTimeout calls (onClick handler at original line 684, setTimeout calls at original lines 694 and 699).
    3. What happened after each step: If the component unmounts before these asynchronous operations or timeouts complete, React warnings ("Can't perform a React state update on an unmounted component") may appear, and functions may attempt to operate on DOM elements that no longer exist. This indicates potential memory leaks.
    validations:
    required: true

  • type: textarea
    id: what-happened
    attributes:
    label: 💥 Outcome Summary
    description: |
    Recap what went wrong in one or two lines.

    Example: "Expected code to run, but got an empty response and no error."
    

    placeholder: Expected ___, but got ___.
    value: |
    Expected the component to clean up without errors, but React state update warnings on an unmounted component and potential errors from DOM manipulation on unmounted refs can occur, indicating memory leaks.
    validations:
    required: true

  • type: textarea
    id: logs
    attributes:
    label: 📄 Relevant Logs or Errors (Optional)
    description: Paste API logs, terminal output, or errors here. Use triple backticks () for code formatting. value: |
    Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
    in CodeBlock (created by ...)
    ...
    ```
    render: shell


🔍 Summary of Issues:

The CodeBlock component in webview-ui/src/components/common/CodeBlock.tsx has multiple instances where asynchronous operations or setTimeout calls are not properly cleaned up if the component unmounts before their completion. This can lead to React state update warnings and potential memory leaks.

1. Async Syntax Highlighting (Original Line 247, CodeBlock_247):

  • The useEffect hook for syntax highlighting calls setHighlightedCode after awaiting Shiki operations.
  • Leak: If unmounted during await, setHighlightedCode acts on an unmounted component.

2. setTimeout for Button Position Update (Original Line 459, CodeBlock_459):

  • A useEffect hook dependent on highlightedCode uses setTimeout to call updateCodeBlockButtonPosition.
  • Leak: Timeout not cleared on unmount or subsequent highlightedCode changes, potentially calling updateCodeBlockButtonPosition on an unmounted component.

3. Nested setTimeout in Collapse/Expand Handler (Original Lines 694, 699, CodeBlock_694):

  • The onClick handler for the collapse/expand button uses nested setTimeout calls for scrollIntoView and updateCodeBlockButtonPosition.
  • Leak: These timeouts are not cleared on unmount, leading to potential DOM manipulation on non-existent elements.

✅ Consolidated Fix:

The following fixes have been applied:

  1. Mounted State Ref (isMountedRef):

    • A useRef (isMountedRef) is used to track the component's mounted status.
    • It's set to true on mount and false in a useEffect cleanup function.
    • All asynchronous setHighlightedCode calls (related to CodeBlock_247) are now conditional on isMountedRef.current.
  2. Timeout Management for Button Position (buttonPositionTimeoutRef):

    • A useRef (buttonPositionTimeoutRef) stores the ID of the setTimeout for updateCodeBlockButtonPosition (related to CodeBlock_459).
    • Any existing timeout is cleared before a new one is set.
    • The timeout is cleared in the useEffect's cleanup function.
  3. Timeout Management for Collapse/Expand (collapseTimeout1Ref, collapseTimeout2Ref):

    • Two useRefs (collapseTimeout1Ref, collapseTimeout2Ref) store the IDs of the nested setTimeouts in the collapse/expand handler (related to CodeBlock_694).
    • Existing timeouts are cleared within the onClick handler before new ones are set.
    • A dedicated useEffect hook with an empty dependency array ensures these timeouts are cleared on component unmount.
    • Checks for codeBlockRef.current are added before DOM manipulations within timeout callbacks.

These changes ensure that asynchronous operations and timeouts are properly managed and cleaned up, preventing state updates or DOM manipulations on unmounted components.

🧪 Combined Confidence: 90%

Metadata

Metadata

Assignees

Labels

Issue - In ProgressSomeone is actively working on this. Should link to a PR soon.bugSomething isn't working

Type

No type

Projects

Status

Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions