rust-lang / rust-analyzer

A Rust compiler front-end for IDEs
https://rust-analyzer.github.io/
Apache License 2.0
14.25k stars 1.61k forks source link

abundance of (certain) assert macros causes lag and crash #15505

Open mwcz opened 1 year ago

mwcz commented 1 year ago

rust-analyzer version: (eg. output of "rust-analyzer: Show RA Version" command, accessible in VSCode via Ctrl/⌘+Shift+P)

r-a version reported by Mason: installed version 2023-08-21

Version directly from r-a

$ nvim/mason/packages/rust-analyzer/rust-analyzer-x86_64-unknown-linux-gnu --version
rust-analyzer 0.3.1631-standalone                                                   

rustc version: (eg. output of rustc -V)

rustc 1.73.0-nightly (33a2c2487 2023-07-12)

relevant settings: (eg. client settings, or environment variables like CARGO, RUSTC, RUSTUP_HOME or CARGO_HOME)

I'm using rust-analyzer in vim via Mason, and here's the settings dump shown by Mason:

rust-analyzer settings

    ◍ rust-analyzer rust_analyzer
      rust-analyzer is an implementation of the Language Server Protocol for the
      Rust programming language. It provides features like completion and goto
      definition for many code editors, including VS Code, Emacs and Vim.

      installed version 2023-08-21                                 
      homepage          https://github.com/rust-lang/rust-analyzer 
      languages         Rust                                       
      categories        LSP                                        
      executables       rust-analyzer                              

      ↓ LSP server configuration schema (press enter to collapse)
        This is a read-only overview of the settings this server accepts. Note that some settings might not apply to neovim.

        → $generated-end                                                       
        → $generated-start                                                     
        → rust-analyzer.assist.emitMustUse                                      default: false
        → rust-analyzer.assist.expressionFillDefault                            default: "todo"
        → rust-analyzer.cachePriming.enable                                     default: true
        → rust-analyzer.cachePriming.numThreads                                 default: 0
        → rust-analyzer.cargo.autoreload                                        default: true
        → rust-analyzer.cargo.buildScripts.enable                               default: true
        → rust-analyzer.cargo.buildScripts.invocationLocation                   default: "workspace"
        → rust-analyzer.cargo.buildScripts.invocationStrategy                   default: "per_workspace"
        → rust-analyzer.cargo.buildScripts.overrideCommand                     
        → rust-analyzer.cargo.buildScripts.useRustcWrapper                      default: true
        → rust-analyzer.cargo.cfgs                                              default: {}
        → rust-analyzer.cargo.extraArgs                                         default: []
        → rust-analyzer.cargo.extraEnv                                          default: {}
        → rust-analyzer.cargo.features                                          default: []
        → rust-analyzer.cargo.noDefaultFeatures                                 default: false
        → rust-analyzer.cargo.sysroot                                           default: "discover"
        → rust-analyzer.cargo.sysrootSrc                                       
        → rust-analyzer.cargo.target                                           
        → rust-analyzer.cargo.unsetTest                                         default: ["core"]
        → rust-analyzer.cargoRunner                                            
        → rust-analyzer.check.allTargets                                        default: true
        → rust-analyzer.check.command                                           default: "check"
        → rust-analyzer.check.extraArgs                                         default: []
        → rust-analyzer.check.extraEnv                                          default: {}
        → rust-analyzer.check.features                                         
        → rust-analyzer.check.ignore                                            default: []
        → rust-analyzer.check.invocationLocation                                default: "workspace"
        → rust-analyzer.check.invocationStrategy                                default: "per_workspace"
        → rust-analyzer.check.noDefaultFeatures                                
        → rust-analyzer.check.overrideCommand                                  
        → rust-analyzer.check.targets                                          
        → rust-analyzer.checkOnSave                                             default: true
        → rust-analyzer.completion.autoimport.enable                            default: true
        → rust-analyzer.completion.autoself.enable                              default: true
        → rust-analyzer.completion.callable.snippets                            default: "fill_arguments"
        → rust-analyzer.completion.limit                                       
        → rust-analyzer.completion.postfix.enable                               default: true
        → rust-analyzer.completion.privateEditable.enable                       default: false
        → rust-analyzer.completion.snippets.custom                              default: {"Arc::new":{"requires":"std::sync::Arc","scope":"expr","body":"Arc::new(${receiver})","postfix":"arc","description":"Put the expression into an `Arc`"},"Rc::new":{"requires":"std::rc::Rc","scope":"expr","body":"Rc::new(${receiver})","postfix":"rc","description":"Put the expression into an `Rc`"},"Err":{"scope":"expr","body":"Err(${receiver})","postfix":"err","description":"Wrap the expression in a `Result::Err`"},"Box::pin":{"requires":"std::boxed::Box","scope":"expr","body":"Box::pin(${receiver})","postfix":"pinbox","description":"Put the expression into a pinned `Box`"},"Ok":{"scope":"expr","body":"Ok(${receiver})","postfix":"ok","description":"Wrap the expression in a `Result::Ok`"},"Some":{"scope":"expr","body":"Some(${receiver})","postfix":"some","description":"Wrap the expression in an `Option::Some`"}}
        → rust-analyzer.debug.engine                                            default: "auto"
        → rust-analyzer.debug.engineSettings                                    default: {}
        → rust-analyzer.debug.openDebugPane                                     default: false
        → rust-analyzer.debug.sourceFileMap                                     default: {"\/rustc\/":"${env:USERPROFILE}\/.rustup\/toolchains\/\/lib\/rustlib\/src\/rust"}
        → rust-analyzer.diagnostics.disabled                                    default: []
        → rust-analyzer.diagnostics.enable                                      default: true
        → rust-analyzer.diagnostics.experimental.enable                         default: false
        → rust-analyzer.diagnostics.previewRustcOutput                          default: false
        → rust-analyzer.diagnostics.remapPrefix                                 default: {}
        → rust-analyzer.diagnostics.useRustcErrorCode                           default: false
        → rust-analyzer.diagnostics.warningsAsHint                              default: []
        → rust-analyzer.diagnostics.warningsAsInfo                              default: []
        → rust-analyzer.discoverProjectCommand                                 
        → rust-analyzer.files.excludeDirs                                       default: []
        → rust-analyzer.files.watcher                                           default: "client"
        → rust-analyzer.highlightRelated.breakPoints.enable                     default: true
        → rust-analyzer.highlightRelated.closureCaptures.enable                 default: true
        → rust-analyzer.highlightRelated.exitPoints.enable                      default: true
        → rust-analyzer.highlightRelated.references.enable                      default: true
        → rust-analyzer.highlightRelated.yieldPoints.enable                     default: true
        → rust-analyzer.hover.actions.debug.enable                              default: true
        → rust-analyzer.hover.actions.enable                                    default: true
        → rust-analyzer.hover.actions.gotoTypeDef.enable                        default: true
        → rust-analyzer.hover.actions.implementations.enable                    default: true
        → rust-analyzer.hover.actions.references.enable                         default: false
        → rust-analyzer.hover.actions.run.enable                                default: true
        → rust-analyzer.hover.documentation.enable                              default: true
        → rust-analyzer.hover.documentation.keywords.enable                     default: true
        → rust-analyzer.hover.links.enable                                      default: true
        → rust-analyzer.hover.memoryLayout.alignment                            default: "hexadecimal"
        → rust-analyzer.hover.memoryLayout.enable                               default: true
        → rust-analyzer.hover.memoryLayout.niches                               default: false
        → rust-analyzer.hover.memoryLayout.offset                               default: "hexadecimal"
        → rust-analyzer.hover.memoryLayout.size                                 default: "both"
        → rust-analyzer.imports.granularity.enforce                             default: false
        → rust-analyzer.imports.granularity.group                               default: "crate"
        → rust-analyzer.imports.group.enable                                    default: true
        → rust-analyzer.imports.merge.glob                                      default: true
        → rust-analyzer.imports.prefer.no.std                                   default: false
        → rust-analyzer.imports.prefix                                          default: "plain"
        → rust-analyzer.inlayHints.bindingModeHints.enable                      default: false
        → rust-analyzer.inlayHints.chainingHints.enable                         default: true
        → rust-analyzer.inlayHints.closingBraceHints.enable                     default: true
        → rust-analyzer.inlayHints.closingBraceHints.minLines                   default: 25
        → rust-analyzer.inlayHints.closureCaptureHints.enable                   default: false
        → rust-analyzer.inlayHints.closureReturnTypeHints.enable                default: "never"
        → rust-analyzer.inlayHints.closureStyle                                 default: "impl_fn"
        → rust-analyzer.inlayHints.discriminantHints.enable                     default: "never"
        → rust-analyzer.inlayHints.expressionAdjustmentHints.enable             default: "never"
        → rust-analyzer.inlayHints.expressionAdjustmentHints.hideOutsideUnsafe  default: false
        → rust-analyzer.inlayHints.expressionAdjustmentHints.mode               default: "prefix"
        → rust-analyzer.inlayHints.lifetimeElisionHints.enable                  default: "never"
        → rust-analyzer.inlayHints.lifetimeElisionHints.useParameterNames       default: false
        → rust-analyzer.inlayHints.maxLength                                    default: 25
        → rust-analyzer.inlayHints.parameterHints.enable                        default: true
        → rust-analyzer.inlayHints.reborrowHints.enable                         default: "never"
        → rust-analyzer.inlayHints.renderColons                                 default: true
        → rust-analyzer.inlayHints.typeHints.enable                             default: true
        → rust-analyzer.inlayHints.typeHints.hideClosureInitialization          default: false
        → rust-analyzer.inlayHints.typeHints.hideNamedConstructor               default: false
        → rust-analyzer.interpret.tests                                         default: false
        → rust-analyzer.joinLines.joinAssignments                               default: true
        → rust-analyzer.joinLines.joinElseIf                                    default: true
        → rust-analyzer.joinLines.removeTrailingComma                           default: true
        → rust-analyzer.joinLines.unwrapTrivialBlock                            default: true
        → rust-analyzer.lens.debug.enable                                       default: true
        → rust-analyzer.lens.enable                                             default: true
        → rust-analyzer.lens.forceCustomCommands                                default: true
        → rust-analyzer.lens.implementations.enable                             default: true
        → rust-analyzer.lens.location                                           default: "above_name"
        → rust-analyzer.lens.references.adt.enable                              default: false
        → rust-analyzer.lens.references.enumVariant.enable                      default: false
        → rust-analyzer.lens.references.method.enable                           default: false
        → rust-analyzer.lens.references.trait.enable                            default: false
        → rust-analyzer.lens.run.enable                                         default: true
        → rust-analyzer.linkedProjects                                          default: []
        → rust-analyzer.lru.capacity                                           
        → rust-analyzer.lru.query.capacities                                    default: {}
        → rust-analyzer.notifications.cargoTomlNotFound                         default: true
        → rust-analyzer.numThreads                                             
        → rust-analyzer.procMacro.attributes.enable                             default: true
        → rust-analyzer.procMacro.enable                                        default: true
        → rust-analyzer.procMacro.ignored                                       default: {}
        → rust-analyzer.procMacro.server                                       
        → rust-analyzer.references.excludeImports                               default: false
        → rust-analyzer.restartServerOnConfigChange                             default: false
        → rust-analyzer.runnables.command                                      
        → rust-analyzer.runnables.extraArgs                                     default: []
        → rust-analyzer.runnables.extraEnv                                     
        → rust-analyzer.runnables.problemMatcher                                default: ["$rustc"]
        → rust-analyzer.rustc.source                                           
        → rust-analyzer.rustfmt.extraArgs                                       default: []
        → rust-analyzer.rustfmt.overrideCommand                                
        → rust-analyzer.rustfmt.rangeFormatting.enable                          default: false
        → rust-analyzer.semanticHighlighting.doc.comment.inject.enable          default: true
        → rust-analyzer.semanticHighlighting.nonStandardTokens                  default: true
        → rust-analyzer.semanticHighlighting.operator.enable                    default: true
        → rust-analyzer.semanticHighlighting.operator.specialization.enable     default: false
        → rust-analyzer.semanticHighlighting.punctuation.enable                 default: false
        → rust-analyzer.semanticHighlighting.punctuation.separate.macro.bang    default: false
        → rust-analyzer.semanticHighlighting.punctuation.specialization.enable  default: false
        → rust-analyzer.semanticHighlighting.strings.enable                     default: true
        → rust-analyzer.server.extraEnv                                        
        → rust-analyzer.server.path                                            
        → rust-analyzer.showDependenciesExplorer                                default: true
        → rust-analyzer.showUnlinkedFileNotification                            default: true
        → rust-analyzer.signatureInfo.detail                                    default: "full"
        → rust-analyzer.signatureInfo.documentation.enable                      default: true
        → rust-analyzer.trace.extension                                         default: false
        → rust-analyzer.trace.server                                            default: "off"
        → rust-analyzer.typing.autoClosingAngleBrackets.enable                  default: false
        → rust-analyzer.typing.continueCommentsOnNewline                        default: true
        → rust-analyzer.workspace.symbol.search.kind                            default: "only_types"
        → rust-analyzer.workspace.symbol.search.limit                           default: 128
        → rust-analyzer.workspace.symbol.search.scope                           default: "workspace"

I'm noticing some dramatic slowness, and crashing, in certain situations with a lot of assert macros.

Here's a minimal reproducer:

fn main() {
    assert_eq!(Some(1), Some(1));
    assert_eq!(Some(1), Some(1));
    assert_eq!(Some(1), Some(1));
    // ... and so on, until there are 7,000 instances of assert_eq
}

With that assert_eq duplicated, r-a runs on my machine for 6-7 minutes, seems to finish processing the crate and works for a few seconds (showing hover info, etc), and then crashes (code 0, signal 6). While running, it pegs two cores at 100%, and memory usage hovers around 640MB.

Having so many asserts seems excessive, but I have a real-wold case where having the asserts is helpful. I can find a workaround, but there seems to be something worth looking at here since other variations of "many asserts" work without a hitch.

As a couterexample, r-a has no trouble at all with this example:

fn foo() -> bool {
    true
}

fn main() {
    assert!(foo()); // <-- again, duplicate this 7,000 times
}
lnicola commented 1 year ago

Looks like we're spinning in resolve_obligations_as_possible. I didn't see it crash, though (I waited 10 minutes, with 10 000 lines, and it didn't finish).

flodiebold commented 1 year ago

That would have been my first guess from the description... The thing is, the obligations shouldn't be accumulating there. So the question is what the obligations are that can't get resolved immediately here.

mwcz commented 1 year ago

Looks like we're spinning in resolve_obligations_as_possible. I didn't see it crash, though (I waited 10 minutes, with 10 000 lines, and it didn't finish).

For me, once it finishes, r-a works for a few seconds, and then crashes.

cuviper commented 1 year ago

Wild guess: maybe it has to do with the integer type inference (and i32 fallback) for each Some(1)? They will be slightly driven by PartialEq as well.

flodiebold commented 1 year ago

Ah that's a good point, yeah, it's very likely related to that.

mwcz commented 1 year ago

Something like Some(true) or Some(()) would avoid type inference and PartialEq, right? I gave that a try, and there's an improvement. r-a no longer crashes, but it does still take 2 minutes before becoming responsive.

mwcz commented 6 months ago

I did some more evaluation and found a few interesting things.

The crash is due to a stack overflow.

$ time rust-analyzer diagnostics . 2>&1 | ts -s
00:00:02 processing crate: rust_analyzer_perf_fix, module: /home/users/projects/rust-analyzer-perf-fix/src/main.rs
00:04:23
00:04:23 thread 'main' has overflowed its stack
00:04:23 fatal runtime error: stack overflow

infer_expr_inner jumps out in a flame graph. It's 94% of the total time, and appears to have been called about 300,000 times.