rust-lang / rust-lang/rust-analyzer

rust-analyzer thread infinite loops when opening file (most of the time)

Open
#21,824 5 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-macro A-nameres Broken Window C-bug I-hang
Dominant language
Rust
Stars
16.9k
Forks
2.2k
Avg merge
1d 12h
Merged PRs (30d)
72

Description

rust-analyzer version: rust-analyzer version: 0.4.2826-standalone

rustc version: rustc 1.91.0-nightly (2e2642e64 2025-08-16)

editor or extension: VSCode, 0.4.2826

relevant settings: N/A

repository link (if public, optional): N/A

code snippet to reproduce:

This depends on the higher-kinded-types crate. The infinite loop happens when rdb.rs is opened, or when the server starts with rdb.rs opened. If rdb.rs isn't opened then the infinite loop does not happen. Rarely, opening rdb.rs does not trigger the infinite loop.

This requires two files (otherwise opening lib.rs will hit the infinite loop), along with the hkt dependency.

Cargo.toml

[package]
name = "soup_repro"
version = "0.1.0"
edition = "2024"

[dependencies]
higher-kinded-types = "0.3.0"

src/lib.rs

use std::fmt::Debug;

pub(crate) mod rdb;

pub struct Arena;

pub trait Base {
  type Error: Debug;
  type PinnedBytes<'a>: AsRef<[u8]>;
}
pub trait Value<'a>: 'a {}

pub trait ComposableValue<'a> {}

pub trait KeyPrefix<'a, K>: Value<'a> {}

pub trait WriteBatch {
  fn size(&self) -> usize;
}

pub trait Cursor<'k, 'v, T: Table<'k, 'v, Error = Self::Error>>: Base + Sized {
  fn move_next(&mut self) -> Result<(), Self::Error>;
  fn move_prev(&mut self) -> Result<(), Self::Error>;

  fn seek_to_first(&mut self) -> Result<(), Self::Error>;
  fn seek<'a>(
    &mut self,
    key: &dyn KeyPrefix<'a, T::Key>,
    arena: &'a Arena,
  ) -> Result<(), Self::Error>;
  fn seek_before<'a>(
    &mut self,
    key: &dyn KeyPrefix<'a, T::Key>,
    arena: &'a Arena,
  ) -> Result<(), Self::Error>;
  fn seek_to_last(&mut self) -> Result<(), Self::Error>;

  fn refresh(&mut self) -> Result<(), Self::Error>;

  fn key(&'k self, arena: &'k Arena) -> Option<T::Key>;
  fn value(&'v self, arena: &'v Arena) -> Option<T::Value>;
  fn key_value<'a: 'k + 'v>(&'a self, arena: &'a Arena) -> Option<(T::Key, T::Value)>;
}

pub trait Table<'k, 'v>: Base + Sized + Clone {
  type Snapshot;
  type WriteBatch;
  type Cursor: Cursor<'k, 'v, Self, Error = Self::Error>;

  type Key: Value<'k>;
  type Value: Value<'v>;

  fn get<'this: 'v, 'a: 'k + 'v>(
    &'this self,
    snap: &Self::Snapshot,
    key: &'k Self::Key,
    arena: &'a Arena,
    pinned: &'v mut Option<Self::PinnedBytes<'this>>,
  ) -> Result<Option<Self::Value>, Self::Error>;

  fn put<'a: 'k + 'v>(
    &self,
    wb: &mut Self::WriteBatch,
    arena: &'a Arena,
    key: &'k Self::Key,
    value: &'v Self::Value,
  ) -> Result<(), Self::Error>;

  fn delete<'a: 'k>(
    &self,
    wb: &mut Self::WriteBatch,
    arena: &'a Arena,
    key: &'k Self::Key,
  ) -> Result<(), Self::Error>;

  fn cursor(&self, snap: &Self::Snapshot) -> Self::Cursor;
}

pub trait TableMerge<'k, 'v>: Table<'k, 'v> {
  type Merge: Value<'v>;

  fn merge<'a: 'k + 'v>(
    &self,
    wb: &mut Self::WriteBatch,
    arena: &'a Arena,
    key: &'k Self::Key,
    value: &'v Self::Merge,
  ) -> Result<(), Self::Error>;
}

pub trait Singleton<'v>: Base + Sized + Clone {
  type Snapshot;
  type WriteBatch;

  type Value: Value<'v>;

  fn get<'this: 'v, 'a: 'v>(
    &'this self,
    snap: &Self::Snapshot,
    arena: &'a Arena,
    pinned: &'v mut Option<Self::PinnedBytes<'this>>,
  ) -> Result<Option<Self::Value>, Self::Error>;

  fn put<'a: 'v>(
    &self,
    wb: &mut Self::WriteBatch,
    arena: &'a Arena,
    value: &'v Self::Value,
  ) -> Result<(), Self::Error>;

  fn delete(&self, wb: &mut Self::WriteBatch, arena: &Arena) -> Result<(), Self::Error>;
}

pub trait SingletonMerge<'v>: Singleton<'v> {
  type Merge: Value<'v>;

  fn merge<'a: 'v>(
    &self,
    wb: &mut Self::WriteBatch,
    arena: &'a Arena,
    value: &'v Self::Merge,
  ) -> Result<(), Self::Error>;
}

pub trait TableParams<D: Db> {
  fn params(&self) -> D::TableParams;
}

pub trait OpenTable<D: Db> {
  type Table: for<'k, 'v> Table<'k, 'v, Error = D::Error>;
  fn open(db: &D) -> Self::Table;
}

pub trait OpenSingleton<D: Db> {
  type Singleton: for<'v> Singleton<'v, Error = D::Error>;
  fn open(self, db: &D) -> Self::Singleton;
}

pub trait Db: Base + Clone {
  type Snapshot;
  type WriteBatch: WriteBatch;

  type Options;
  type TableParams;

  fn new(
    path: &std::path::Path,
    opts: &Self::Options,
    tables: &[&dyn TableParams<Self>],
  ) -> Result<Self, Self::Error>;

  fn new_read_only(
    path: &std::path::Path,
    opts: &Self::Options,
    tables: &[&dyn TableParams<Self>],
  ) -> Result<Self, Self::Error>;

  fn snapshot(&self) -> Self::Snapshot;

  fn new_write_batch(&self) -> Self::WriteBatch;
  fn commit(&self, wb: Self::WriteBatch) -> Result<(), Self::Error>;
  fn commit_no_wal(&self, wb: Self::WriteBatch) -> Result<(), Self::Error>;
}

src/rdb.rs

use std::{marker::PhantomData, sync::Arc};

use higher_kinded_types as hkt;

use crate::*;

mod rocksdb {
  pub struct MultiThreaded;
  pub type DBWithThreadMode<T> = std::marker::PhantomData<T>;
  pub struct WriteBatch;
  pub type SnapshotWithThreadMode<'a, T> = std::marker::PhantomData<T>;
  pub type DBRawIteratorWithThreadMode<'a, T> = std::marker::PhantomData<T>;
  pub type Error = ();
  pub type DBPinnableSlice<'a> = &'a [u8];
  pub type BoundColumnFamily<'a> = ();
  pub type ReadOptions = ();
  pub type Options = ();
  pub type ColumnFamilyDescriptor = ();
}

// RocksDB Database Implementation:

#[derive(Clone)]
pub(crate) struct RocksDb(pub(crate) Arc<rocksdb::DBWithThreadMode<rocksdb::MultiThreaded>>);

impl WriteBatch for rocksdb::WriteBatch {
  fn size(&self) -> usize {
    0
  }
}

pub(crate) struct RocksDbSnapshot(
  rocksdb::SnapshotWithThreadMode<'static, rocksdb::DBWithThreadMode<rocksdb::MultiThreaded>>,
  RocksDb,
);

pub(crate) struct RocksDbCursor<T> {
  db: Arc<rocksdb::DBWithThreadMode<rocksdb::MultiThreaded>>,
  pub(crate) iter: rocksdb::DBRawIteratorWithThreadMode<
    'static,
    rocksdb::DBWithThreadMode<rocksdb::MultiThreaded>,
  >,
  table: PhantomData<T>,
}

impl<T> Base for RocksDbCursor<T> {
  type Error = rocksdb::Error;
  type PinnedBytes<'a> = rocksdb::DBPinnableSlice<'a>;
}

impl<'k, 'v, T: Table<'k, 'v, Error = Self::Error>> Cursor<'k, 'v, T> for RocksDbCursor<T> {
  fn move_next(&mut self) -> Result<(), Self::Error> {
    unimplemented!()
  }

  fn move_prev(&mut self) -> Result<(), Self::Error> {
    unimplemented!()
  }

  fn seek_to_first(&mut self) -> Result<(), Self::Error> {
    unimplemented!()
  }

  fn seek<'a>(
    &mut self,
    key: &dyn KeyPrefix<'a, T::Key>,
    arena: &'a Arena,
  ) -> Result<(), Self::Error> {
    unimplemented!()
  }

  fn seek_before<'a>(
    &mut self,
    key: &dyn KeyPrefix<'a, T::Key>,
    arena: &'a Arena,
  ) -> Result<(), Self::Error> {
    unimplemented!()
  }

  fn seek_to_last(&mut self) -> Result<(), Self::Error> {
    unimplemented!()
  }

  fn refresh(&mut self) -> Result<(), Self::Error> {
    unimplemented!()
  }

  fn key(&'k self, arena: &'k Arena) -> Option<T::Key> {
    unimplemented!()
  }

  fn value(&'v self, arena: &'v Arena) -> Option<T::Value> {
    unimplemented!()
  }

  fn key_value<'a: 'k + 'v>(&'a self, arena: &'a Arena) -> Option<(T::Key, T::Value)> {
    unimplemented!()
  }
}

pub(crate) struct RocksDbTable<M, K, V> {
  pub(crate) cf: Arc<rocksdb::BoundColumnFamily<'static>>,
  pub(crate) db: RocksDb,
  pub(crate) _pd: PhantomData<(M, K, V)>,
}

impl<M, K, V> Clone for RocksDbTable<M, K, V> {
  fn clone(&self) -> Self {
    Self {
      cf: self.cf.clone(),
      db: self.db.clone(),
      _pd: PhantomData,
    }
  }
}

impl<M, K, V> Base for RocksDbTable<M, K, V> {
  type Error = rocksdb::Error;
  type PinnedBytes<'a> = rocksdb::DBPinnableSlice<'a>;
}

impl<'k, 'v, M, K: for<'a> hkt::ForLt<Of<'a>: Value<'a>>, V: for<'a> hkt::ForLt<Of<'a>: Value<'a>>>
  Table<'k, 'v> for RocksDbTable<M, K, V>
{
  type Snapshot = RocksDbSnapshot;
  type WriteBatch = rocksdb::WriteBatch;
  type Cursor = RocksDbCursor<Self>;

  type Key = K::Of<'k>;
  type Value = V::Of<'v>;

  fn get<'this: 'v, 'a: 'k + 'v>(
    &'this self,
    snap: &Self::Snapshot,
    key: &'k Self::Key,
    arena: &'a Arena,
    pinned: &'v mut Option<Self::PinnedBytes<'this>>,
  ) -> Result<Option<Self::Value>, Self::Error> {
    unimplemented!()
  }

  fn put<'a: 'k + 'v>(
    &self,
    wb: &mut Self::WriteBatch,
    arena: &'a Arena,
    key: &'k Self::Key,
    value: &'v Self::Value,
  ) -> Result<(), Self::Error> {
    unimplemented!()
  }

  fn delete<'a: 'k>(
    &self,
    wb: &mut Self::WriteBatch,
    arena: &'a Arena,
    key: &'k Self::Key,
  ) -> Result<(), Self::Error> {
    unimplemented!()
  }

  fn cursor(&self, snap: &Self::Snapshot) -> Self::Cursor {
    unimplemented!()
  }
}

pub(crate) struct RocksDbSingleton<M, V> {
  pub(crate) key: &'static [u8],
  pub(crate) table: RocksDbTable<(), hkt::ForLt!(&[u8]), V>,
  pub(crate) _pd: PhantomData<M>,
}

impl<M, V> Clone for RocksDbSingleton<M, V> {
  fn clone(&self) -> Self {
    let Self {
      key,
      ref table,
      _pd,
    } = *self;
    Self {
      key,
      table: table.clone(),
      _pd,
    }
  }
}

impl<M, V> Base for RocksDbSingleton<M, V> {
  type Error = rocksdb::Error;
  type PinnedBytes<'a> = rocksdb::DBPinnableSlice<'a>;
}

impl<'v, M, V: for<'a> hkt::ForLt<Of<'a>: Value<'a>>> Singleton<'v> for RocksDbSingleton<M, V> {
  type Snapshot = RocksDbSnapshot;
  type WriteBatch = rocksdb::WriteBatch;

  type Value = V::Of<'v>;

  fn get<'this: 'v, 'a: 'v>(
    &'this self,
    snap: &Self::Snapshot,
    arena: &'a Arena,
    pinned: &'v mut Option<Self::PinnedBytes<'this>>,
  ) -> Result<Option<Self::Value>, Self::Error> {
    unimplemented!()
  }

  fn put<'a: 'v>(
    &self,
    wb: &mut Self::WriteBatch,
    arena: &'a Arena,
    value: &'v Self::Value,
  ) -> Result<(), Self::Error> {
    unimplemented!()
  }

  fn delete(&self, wb: &mut Self::WriteBatch, arena: &Arena) -> Result<(), Self::Error> {
    unimplemented!()
  }
}

impl Base for RocksDb {
  type Error = rocksdb::Error;
  type PinnedBytes<'a> = rocksdb::DBPinnableSlice<'a>;
}

impl Db for RocksDb {
  type Snapshot = RocksDbSnapshot;
  type WriteBatch = rocksdb::WriteBatch;

  type Options = rocksdb::Options;
  type TableParams = rocksdb::ColumnFamilyDescriptor;

  fn new(
    path: &std::path::Path,
    opts: &Self::Options,
    tables: &[&dyn TableParams<Self>],
  ) -> Result<Self, Self::Error> {
    unimplemented!()
  }

  fn new_read_only(
    path: &std::path::Path,
    opts: &Self::Options,
    tables: &[&dyn TableParams<Self>],
  ) -> Result<Self, Self::Error> {
    unimplemented!()
  }

  fn snapshot(&self) -> Self::Snapshot {
    unimplemented!()
  }

  fn new_write_batch(&self) -> Self::WriteBatch {
    unimplemented!()
  }

  fn commit(&self, wb: Self::WriteBatch) -> Result<(), rocksdb::Error> {
    unimplemented!()
  }

  fn commit_no_wal(&self, wb: Self::WriteBatch) -> Result<(), rocksdb::Error> {
    unimplemented!()
  }
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the hang in VSCode using the Cargo.toml dependency and the examples in src/lib.rs and src/rdb.rs; opening rdb.rs should trigger the rust-analyzer infinite loop. Start by comparing behavior with and without rdb.rs open, then identify the analyzer path involved. Done means opening rdb.rs no longer causes the thread to loop indefinitely.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.