sublimelsp / LSP-rust-analyzer

Convenience package for rust-analyzer

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Rust-analyzer node process goes berzerk, takes 100% CPU when I build in a separate window

mcclure opened this issue · comments

I have a rust project that uses wgpu. WGPU results in the target/ being very very large, like 4GB even though the executable is much smaller.

I have noticed whenever I do a cargo.exe run or cargo.exe build on this project in a terminal window, if I have Sublime Text open, it suddenly slams to 100% CPU and stays there for some time. This causes problems for the build, which is noticeably slowed down by the Sublime Text CPU peg, as well as for my program, as it has a very low framerate right after it opens (due to the CPU being taken up by Sublime). If I do "LSP: Disable Language Server" before I do the build, I do not seem to see this problem. My Rust helper plugin is preventing me from running Rust.

If I have Task Manger open, I see basically all of this stems from a nodejs subprocess of Sublime Text:

image

If I look in Process Explorer, I see the problematic node is executing something called "lsp-file-watcher-chokidar", which I am told is a common helper for sublime lsp for watching for file changes.

I believe that rust-analyzer has spawned this chokidar to check for .rs files changing, but it has failed to configure the target/ directory to be ignored. Therefore, when my build starts changing many files rapidly inside target/, it does a lot of unnecessary work.

Expected behavior: Because rust-analyzer is designed to assist with Rust, it should predict the target/ directory exists and configure chokidar to ignore it by default. In general it should be possible to configure lsp-rust-analyzer to ignore build artifact directories like target/, pkg/ or (for wasm-bindgen projects) node_modules/.

Configuration: I am on Windows 10 Build 19045. I have Rust 1.69.0, Sublime Text 4143, LSP v1.23.0, LSP-rust-analyzer v1.3.1, LSP-file-watcher-chokidar v1.0.2. My settings contains

	"binary_file_patterns": ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.ttf", "*.tga", "*.dds", "*.ico", "*.eot", "*.pdf", "*.swf", "*.jar", "*.zip", "target/*", "pkg/*", "node_modules/*"],
	"index_exclude_patterns": ["target/*", "pkg/*", "node_modules/*"],

Without index_exclude_patterns, Sublime Text does its own chugging during the build, but with this setting added it acts normal.

I also tried adding this to the lsp-rust-analyzer settings and restarting:

	"rust-analyzer.files.excludeDirs": ["target/", "pkg/", "node_modules/"],

But it had no effect on the problem.

In addition, chokidar actually does seem to do quite a bit of activity when I do hg commit. As Sublime does, rust-analyzer should also probably by default be ignoring common version control directories such as .hg, .git etc.

Rust registers watcher for all *.rs files in all directories within the project basically:

{
  "registrations": [
    {
      "id": "workspace/didChangeWatchedFiles",
      "method": "workspace/didChangeWatchedFiles",
      "registerOptions": {
        "watchers": [
          {
            "globPattern": "/usr/local/workspace/temp/rs-hello/**/*.rs"
          },
          {
            "globPattern": "/usr/local/workspace/temp/rs-hello/**/Cargo.toml"
          },
          {
            "globPattern": "/usr/local/workspace/temp/rs-hello/**/Cargo.lock"
          }
        ]
      }
    }
  ]
}

There is only one *.rs file (rs-hello/target/debug/build/thiserror-3f11b3c4b8efe876/out/probe.rs) in the target directory here but I guess setting up a watcher for that directory could still cause extra CPU usage.

I could experiment with overriding the pattern and checking if it still reproduces but I would need a better way to reproduce because here building takes very short time and I don't see a major increase in CPU usage. Also the build directory takes just ~300MB here. Also cargo build takes basically no time if project is already built. I've tried cargo clean first and then cargo build but it still doesn't really reproduce.

In addition, chokidar actually does seem to do quite a bit of activity when I do hg commit. As Sublime does, rust-analyzer should also probably by default be ignoring common version control directories such as .hg, .git etc.

By default patterns from those global ST settings are ignored:

	// folder_exclude_patterns and file_exclude_patterns control which files are
	// listed in folders on the sidebar. These can also be set on a per-
	// project basis.
	"folder_exclude_patterns": [".svn", ".git", ".hg", "CVS", ".Trash", ".Trash-*"],
	"file_exclude_patterns": ["*.pyc", "*.pyo", "*.exe", "*.dll", "*.obj","*.o", "*.a", "*.lib", "*.so", "*.dylib", "*.ncb", "*.sdf", "*.suo", "*.pdb", "*.idb", ".DS_Store", ".directory", "desktop.ini", "*.class", "*.psd", "*.db", "*.sublime-workspace"],

But note that ignoring doesn't mean that the directory won't be watched. It just means that any matching file changes will be filtered out and won't be reported to the LSP server. If the main watch pattern includes those then the OS-specific file watching implementation will likely still watch those and might cause higher CPU usage. It would have to be verified whether that's the case though and it also can differ per OS.

There is only one *.rs file (rs-hello/target/debug/build/thiserror-3f11b3c4b8efe876/out/probe.rs) in the target directory here but I guess setting up a watcher for that directory could still cause extra CPU usage.

As you can see in the screenshot, rust-analyzer.exe is taking up little or no CPU during this time. All the CPU cost is chokidar (the watcher). So the problem is not probe.rs, it's the directory as a whole.

I think it would make sense to put target into "folder_exclude_patterns". However, if Chokidar is expending significant resource usage even on the already excluded folders such as .hg and contents of "folder_exclude_patterns", then maybe the bug is on chokidar or lsp-file-watcher-chokidar. Maybe I should be filing there :(

ignoring doesn't mean that the directory won't be watched. It just means that any matching file changes will be filtered out and won't be reported to the LSP server

Looking, I find chokidar has a documented "ignored" option in its options structure.

Looking, I find LSP-file-watcher-chokidar has an "ignores" option in its options structure which gets translated to chokidar "ignored".

It sounds like these flags should be enough to prevent the behavior I am trying to avoid (heavy CPU usage from chokidar watching unimportant directories).

I am not familiar with the stack but looking at the LSP-rust-analyzer code I do not find lsp-file-watcher-chokidar's "ignores" getting set, and based on your comment it seems you believe you are not setting such a flag. Do (lsp-rust-analyzer settings / ST global) folder_exclude_patterns get passed to LSP-file-watcher-chokidar "ignores"? If not, could they be?

There is only one *.rs file (rs-hello/target/debug/build/thiserror-3f11b3c4b8efe876/out/probe.rs) in the target directory here but I guess setting up a watcher for that directory could still cause extra CPU usage.

As you can see in the screenshot, rust-analyzer.exe is taking up little or no CPU during this time. All the CPU cost is chokidar (the watcher). So the problem is not probe.rs, it's the directory as a whole.

I think it would make sense to put target into "folder_exclude_patterns". However, if Chokidar is expending significant resource usage even on the already excluded folders such as .hg and contents of "folder_exclude_patterns", then maybe the bug is on chokidar or lsp-file-watcher-chokidar. Maybe I should be filing there :(

You can try yourself putting target in global folder_exclude_patterns. You will not see that folder in ST's project view also then. And as a bonus avoid ST indexing it.
(Note that a better place for adding this exclude would be project settings but unfortunately LSP doesn't respect those for the purpose of file watching currently.)

I am not familiar with the stack but looking at the LSP-rust-analyzer code I do not find lsp-file-watcher-chokidar's "ignores" getting set, and based on your comment it seems you believe you are not setting such a flag. Do (lsp-rust-analyzer settings / ST global) folder_exclude_patterns get passed to LSP-file-watcher-chokidar "ignores"? If not, could they be?

Base LSP package creates the watcher in: https://github.com/sublimelsp/LSP/blob/0d149265408824b73184709619af4497d9044650/plugin/core/sessions.py#L1492-L1493

If ignores is not specified in watcher settings then it defaults to global ignore rules: https://github.com/sublimelsp/LSP/blob/0d149265408824b73184709619af4497d9044650/plugin/core/sessions.py#L1507-L1518

It sounds like these flags should be enough to prevent the behavior I am trying to avoid (heavy CPU usage from chokidar watching unimportant directories).

As I've mentioned before, setting ignores might not help the CPU usage if the parent folder is being watched. Depending on OS-specific implementation, the watcher will likely still need to be set up and include all child folders. Only that chokidar will filter out those event later. Windows might be especially inefficient here but I'm speculating.

Okay, so there are a few things going on here. There are two questions I think are worth asking:

  1. Is the interface for selecting to-ignore directories (and the defaults) in LSP-rust-analyzer the appropriate one?
  2. Once LSP-rust-analyzer has set the ignore directories, does Chokidar properly ignore them?

You can try yourself putting target in global folder_exclude_patterns. You will not see that folder in ST's project view also then. And as a bonus avoid ST indexing it. (Note that a better place for adding this exclude would be project settings but unfortunately LSP doesn't respect those for the purpose of file watching currently.)

Talking about question 1: Sublime Text has three different ignore settings.

  • "binary_file_patterns": Sublime Text will track the existence of these files, but will not look inside when doing text operations (for example find files).
  • "index_exclude_patterns": "indicate which files won't be indexed"— these files will appear in the sidebar, but Sublime will not track changes to them.
  • "folder_exclude_patterns": Sublime ignores completely, they are not even visible in sidebar.

My "expected behavior" (in addition to including target/, and maybe pkg/ and node_modules/ in the ignore defaults) is that you should be passing folder_exclude_patterns and index_exclude_patterns to Chokidar to be ignored. Using my configuration as an example, I have .hg in folder_exclude_patterns, but I have target/ in index_exclude_patterns but not folder_exclude_patterns (because I don't want it to track those files, but I might occasionally want to inspect a build product in the sidebar).

Question 2— I will try testing putting target/ in folder_exclude_patterns and see if anything changes. If the problem persists I will file that bug on chokidar because I think there are ways they could handle this correctly regardless of OS.

Question 2— I will try testing putting target/ in folder_exclude_patterns and see if anything changes. If the problem persists I will file that bug on chokidar because I think there are ways they could handle this correctly regardless of OS.

That sounds very optimistic :)
Chokidar is 11 years old. I'd think that they have figured out the most efficient ways of handling things by now.

Its readme states:

On most other platforms, the fs.watch-based implementation is the default, which avoids polling and keeps CPU usage down. Be advised that chokidar will initiate watchers recursively for everything within scope of the paths that have been specified, so be judicious about not wasting system resources by watching much more than needed.

The pattern that rust-analyzer chooses to watch is [...]/rs-hello/**/*.rs which means that the whole rs-hello folder and all subfolders need to be watched recursively. Even if target directory is excluded using ignores, I'm pretty sure that the implemenation might have to watch it anyway and just filter out all events after they happen.

But feel free to create issue there to discuss it.
You can probably reproduce the same using chokidar-cli which would be easier for authors of chokidar to reason about.

BTW. Maybe you can tell rust to create build files outside of the project directory? Then you could workaround the issue that way.