Skip to content

[BUG] 90%+ idle CPU after first MCP tool call — anyio cancel scope busy-loop in streamable HTTP client #1226

Open

Description

@major

Description

After the first request that triggers an MCP tool call, the lightspeed-stack process burns 90%+ CPU indefinitely while fully idle (no incoming requests). Restarting the container resolves it until the next MCP tool call.

Environment

  • lightspeed-stack image: quay.io/lightspeed-core/lightspeed-stack:dev-20260225-f6aa46b
  • llama-stack: 0.4.3
  • MCP SDK (mcp): 1.26.0
  • anyio: 4.12.1
  • Python: 3.12.12
  • Config: library mode, 1 worker, MCP server via streamable HTTP transport

Reproduction

  1. Start lightspeed-stack with an MCP server configured (streamable HTTP transport)
  2. Send any request that triggers MCP tool use (e.g. POST /v1/infer)
  3. Wait for the response to complete
  4. Observe CPU usage — the process stays at 90%+ with no further requests

Root Cause

py-spy profiling shows the main thread stuck in a tight loop:

Thread 1 (active+gil): "MainThread"
    _call_soon (asyncio/base_events.py:819)
    call_soon (asyncio/base_events.py:803)
    _deliver_cancellation (anyio/_backends/_asyncio.py:594)
    _run (asyncio/events.py:88)
    _run_once (asyncio/base_events.py:1999)
    run_forever (asyncio/base_events.py:645)

After an MCP tool call completes, the MCP streamable HTTP client's cleanup calls tg.cancel_scope.cancel(). Tasks in the scope are stuck in _must_cancel state (they're trying to write to already-closed streams), causing AnyIO's _deliver_cancellation() to re-schedule itself via call_soon() on every event loop tick without ever converging.

This is an upstream issue in the MCP Python SDK: modelcontextprotocol/python-sdk#1805. I've added confirming profiling evidence there: modelcontextprotocol/python-sdk#1805 (comment)

Evidence

podman stats with no active requests:

NAME              CPU %
lightspeed-stack  59.30%

podman top lightspeed-stack:

PID  PPID  %CPU  COMMAND
  1     0  90.1  python3.12 src/lightspeed_stack.py

3 consecutive py-spy samples (2/3 caught in _deliver_cancellation):

Sample 1: _deliver_cancellation (line 594) → call_soon
Sample 2: _deliver_cancellation (line 578)
Sample 3: select (momentarily idle between bursts)

Workaround

Restarting the lightspeed-stack container resolves the issue until the next MCP tool call.

Activity

  1. akshan-main commented on Feb 25, 2026

    @akshan-main

    The upstream fix for this is at modelcontextprotocol/python-sdk#2147. Upgrading the mcp dependency once that's merged should resolve the idle CPU spike.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions