Skip to content

100% CPU busy-loop in edit_prediction_context::identifiers_for_position → OutlineItem::body_range (idle editor, Zed 1.4.4) #58152

@maxguzenski

Description

@maxguzenski

Summary

Editor sitting idle, one CPU core pinned at ~100% indefinitely. A single background worker thread (Worker-N from the gpui_linux dispatcher pool) is stuck in a pure userspace busy loop inside edit-prediction context gathering — specifically edit_prediction_context::identifiers_for_positionlanguage::outline::OutlineItem::body_range.

Related to #57585 (same surface symptom — 100% CPU with tree_sitter frames on top of the stack), but the driver is a different subsystem, so filing separately. In #57585 the loop is SyntaxSnapshot::reparse_with_rangests_parser__recover; that frame does not appear here. This is either a distinct bug or a second way to reach a similar hot path.

Investigation performed by Claude Opus 4.8 running at extra-high reasoning effort ("xhigh"), driving a live gdb session via Claude Code on the affected machine.

Environment

  • Zed: 1.4.4 (Arch Linux package zed 1.4.4-1, build-id 2f6ddc46bd6985b94e87fb559fe41050c22cbf1b) — newer than the v1.2.6+stable in 100% CPU (tree-sitter reparse loop) #57585
  • OS: Arch Linux, Wayland (Hyprland), gpui_linux
  • Build toolchain (from debuginfo): rustc 1.95.0, tree-sitter 0.26.9
  • Project open was an Elixir project (ElixirLS v0.30.0), but the loop is language-agnostic — it lives in edit-prediction context gathering, not in the language server (the LSP process was at 0% CPU, mix_compile_noop).

Symptom

Editor idle, one core at ~100% indefinitely. A single background worker thread is in state R continuously. Nothing is logged while CPU stays pinned.

How it was diagnosed

  1. ps//proc showed the CPU was burned by the zed-editor process itself, in one background worker thread — not the language server (Elixir beam.smp at 0% CPU, mix_compile_noop).
  2. /proc/<pid>/task/<tid>/syscall was empty and wchan == 0 → a pure userspace busy loop, no I/O, no blocking. The Zed log had gone silent while CPU stayed at 100% → the loop logs nothing.
  3. Attached gdb to the spinning thread (selected by LWP) and symbolized with Arch debuginfod. Sampled the stack 3× — every sample landed in the same call chain (identifiers_for_positionbody_range → tree-sitter cursor walk).

Backtrace (symbolized)

#0  tree_sitter::{impl#97}::partial_cmp                       tree-sitter-0.26.9/binding_rust/lib.rs:96
#1  core::cmp::PartialOrd::le<tree_sitter::Point, …>          core/src/cmp.rs:1429
#2  language::outline::OutlineItem<usize>::body_range         crates/language/src/outline.rs:65
#3  edit_prediction_context::identifiers_for_position         crates/edit_prediction_context/src/edit_prediction_context.rs:636
#4  edit_prediction_context::{impl#1}::fetch_excerpts::{async_fn#0}::{async_block#2}
                                                              crates/edit_prediction_context/src/edit_prediction_context.rs:235
#5  core::future::future::…::poll
#6  async_task::raw::RawTask::run
#7  async_task::runnable::Runnable::run
#8  gpui_linux::linux::dispatcher::…::{closure#0}             crates/gpui_linux/src/linux/dispatcher.rs:57
    … std thread start / clone3

The 3 samples landed at slightly different PCs but always inside frames #2#3: …::identifiers_for_positionOutlineItem::body_rangetree_sitter cursor walk (goto_first_child_for_point, start_point, Point::partial_cmp).

Root cause (analysis)

The hot loop is in edit-prediction context gathering: edit_prediction_context::identifiers_for_position (edit_prediction_context.rs:636), invoked from fetch_excerpts (:235), repeatedly calling language::outline::OutlineItem::body_range (outline.rs:65). tree_sitter appears at the top of the stack only because body_range walks the syntax tree and compares tree_sitter::Point ranges — but the driver of the spin is the edit-prediction path, not a SyntaxSnapshot reparse. This looks like a non-terminating (or pathological) iteration over outline items / tree-cursor positions while computing identifier excerpts around the cursor.

Workaround (confirmed it stops recurrence)

Disable edit predictions and restart Zed:

// settings.json
"features": { "edit_prediction_provider": "none" }

Note: the already-spinning worker does not recover on its own (it's a synchronous loop that never yields, so it won't re-read settings) — Zed must be restarted to clear the current hang. With edit predictions off, the loop does not reoccur.

For maintainers

Likely worth guarding identifiers_for_position / OutlineItem::body_range against the input shape that makes the tree-cursor walk fail to advance. Happy to provide more detail or additional gdb captures.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:ai/edit predictionUmbrella label for Copilot, Supermaven, etc. inline completions (non-Zeta-specific)frequency:uncommonBugs that happen for a small subset of users, special configurations, rare circumstances, etcpriority:P1Security holes w/o exploit, crash, install/update, sign-in, badly broken common featuresstate:needs reproNeeds reproduction steps and/or someone to reproduce

    Type

    No fields configured for Crash.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions