rust-lang / rust-analyzer

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

VSCode Extension Host Unexpectedly Terminated #12572

Open woody77 opened 2 years ago

woody77 commented 2 years ago

rust-analyzer version: rust-analyzer version: 0.0.0 (519d7484f 2022-06-15)

vscode version info: Version: 1.68.1 Commit: 30d9c6cd9483b2cc586687151bcbcd635f373630 Date: 2022-06-14T12:52:13.188Z (3 days ago) Electron: 17.4.7 Chromium: 98.0.4758.141 Node.js: 16.13.0 V8: 9.8.177.13-electron.0 OS: Darwin x64 21.5.0

A number of our developers (including myself) are seeing frequent crashes of the VSCode Extension Host. I've narrowed it down to RA, using extension bisect. I haven't been able to reproduce with a smaller codebase, however.

The issue usually occurs while editing a Rust source file, but I have had a few reports of developers just opening VSCode, and watching the extension host crash immediately in a loop (with Rust source files open, to trigger loading of the extension).

Stack traces from the extension host are not terribly helpful, as it's OOM'ing, and the stack is different for each occurence:

[2022-06-15 12:52:57.896] [remoteagent] [info] [127.0.0.1][0b63be2f][ExtensionHostConnection] <2600411><stderr>
<--- Last few GCs --->

[2600411:0x5ec92b0]    44558 ms: Mark-sweep (reduce) 533.4 (585.4) -> 533.4 (553.4) MB, 991.6 / 0.0 ms  (average mu = 0.514, current mu = 0.014) last resort GC in old space requested
[2600411:0x5ec92b0]    45452 ms: Mark-sweep (reduce) 533.4 (553.4) -> 533.4 (553.2) MB, 894.5 / 0.0 ms  (average mu = 0.351, current mu = 0.000) last resort GC in old space requested

<--- JS stacktrace --->

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory

  1: 0xb02ec0 node::Abort() [~/.vscode-server/bin/4af164ea3a06f701fe3e89a2bcbb421d2026b68f/node]
  2: 0xa181fb node::FatalError(char const*, char const*) [~/.vscode-server/bin/4af164ea3a06f701fe3e89a2bcbb421d2026b68f/node]
  3: 0xced88e v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [~/.vscode-server/bin/4af164ea3a06f701fe3e89a2bcbb421d2026b68f/node]
  4: 0xcedc07 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [~/.vscode-erver/bin/4af164ea3a06f701fe3e89a2bcbb421d2026b68f/node]
  5: 0xea5ea5  [~/.vscode-server/bin/4af164ea3a06f701fe3e89a2bcbb421d2026b68f/node]
  6: 0xeb8328 v8::internal::Heap::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [~/.vscode-server/bin/4af164ea3a06f701fe3e89a2bcbb421d2026b68f/node]
  7: 0xe85916 v8::internal::Factory::CodeBuilder::AllocateCode(bool) [~/.vscode-server/bin/4af164ea3a06f701fe3e89a2bcbb421d2026b68f/node]
  8: 0xe86625 v8::internal::Factory::CodeBuilder::BuildInternal(bool) [~/.vscode-server/bin/4af164ea3a06f701fe3e89a2bcbb421d2026b68f/node]
  9: 0xe8703e v8::internal::Factory::CodeBuilder::Build() [~/.vscode-server/bin/4af164ea3a06f701fe3e89a2bcbb421d2026b68f/node]
 10: 0x14d698e v8::internal::RegExpMacroAssemblerX64::GetCode(v8::internal::Handle<v8::internal::String>) [~/.vscode-server/bin/4af164ea3a06f701fe3e89a2bcbb421d2026b68f/node]
 11: 0x11c12a9 v8::internal::RegExpCompiler::Assemble(v8::internal::Isolate*, v8::internal::RegExpMacroAssembler*, v8::internal::RegExpNode*, int, v8::internal::Handle<v8::internal::String>) [/usr/local/google/home/aaronwood/.vscode-server/bin/4af164ea3a06f701fe3e89a2bcbb421d2026b68f/node]
 12: 0x11df637 v8::internal::RegExpImpl::Compile(v8::internal::Isolate*, v8::internal::Zone*, v8::internal::RegExpCompileData*, v8::base::Flags<v8::internal::JSRegExp::Flag, int>, v8::internal::Handle<v8::internal::String>, v8::internal::Handle<v8::internal::String>, bool, unsigned int&) [~/.vscode-server/bin/4af164ea3a06f701fe3e89a2bcbb421d2026b68f/node]
 13: 0x11dfde0 v8::internal::RegExpImpl::CompileIrregexp(v8::internal::Isolate*, v8::internal::Handle<v8::internal::JSRegExp>, v8::internal::Handle<v8::internal::String>, bool) [~/.vscode-server/bin/4af164ea3a06f701fe3e89a2bcbb421d2026b68f/node]
 14: 0x11e09de v8::internal::RegExpImpl::IrregexpPrepare(v8::internal::Isolate*, v8::internal::Handle<v8::internal::JSRegExp>, v8::internal::Handle<v8::internal::String>) [~/.vscode-server/bin/4af164ea3a06f701fe3e89a2bcbb421d2026b68f/node]
 15: 0x11e0b57 v8::internal::RegExpImpl::IrregexpExec(v8::internal::Isolate*, v8::internal::Handle<v8::internal::JSRegExp>, v8::internal::Handle<v8::internal::String>, int, v8::internal::Handle<v8::internal::RegExpMatchInfo>, v8::internal::RegExp::ExecQuirks) [~/.vscode-server/bin/4af164ea3a06f701fe3e89a2bcbb421d2026b68f/node]
 16: 0x1205d28 v8::internal::Runtime_RegExpExec(int, unsigned long*, v8::internal::Isolate*) [~/.vscode-server/bin/4af164ea3a06f701fe3e89a2bcbb421d2026b68f/node]
 17: 0x15e7879  [~/.vscode-server/bin/4af164ea3a06f701fe3e89a2bcbb421d2026b68f/node]
Extension Host Process exited with code: null, signal: SIGABRT.
woody77: And I just reproduced in a local instance, instead of vscode remote:

<--- Last few GCs --->
[79756:0x7fb898008000]    99612 ms: Mark-sweep (reduce) 408.3 (447.7) -> 408.2 (431.9) MB, 271.0 / 0.0 ms  (average mu = 0.743, current mu = 0.031) last resort GC in old space requested
[79756:0x7fb898008000]    99902 ms: Mark-sweep (reduce) 408.2 (431.9) -> 408.2 (431.7) MB, 289.9 / 0.0 ms  (average mu = 0.581, current mu = 0.000) last resort GC in old space requested
<--- JS stacktrace --->
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
 1: 0x11f09cf85 node::FatalException(v8::Isolate*, v8::TryCatch const&) [/Applications/Visual Studio Code.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework]
 2: 0x11f09d12f node::OnFatalError(char const*, char const*) [/Applications/Visual Studio Code.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework]
 3: 0x11a6638b3 v8::internal::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*) [/Applications/Visual Studio Code.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework]
 4: 0x11a66380f v8::internal::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*) [/Applications/Visual Studio Code.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework]
 5: 0x11a82d7e5 v8::internal::Heap::StartIncrementalMarking(int, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/Applications/Visual Studio Code.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework]
 6: 0x11a83fcdc v8::internal::Heap::AllocatedExternalMemorySinceMarkCompact() [/Applications/Visual Studio Code.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework]
 7: 0x11a802de6 v8::internal::Factory::CodeBuilder::BuildInternal(bool) [/Applications/Visual Studio Code.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework]
 8: 0x11a8032de v8::internal::Factory::CodeBuilder::Build() [/Applications/Visual Studio Code.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework]
 9: 0x11aeea0a5 v8::internal::RegExpMacroAssemblerX64::GetCode(v8::internal::Handle<v8::internal::String>) [/Applications/Visual Studio Code.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework]
10: 0x11ac42afa v8::internal::CharacterRange::IsCanonical(v8::internal::ZoneList<v8::internal::CharacterRange>*) [/Applications/Visual Studio Code.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework]
11: 0x11ac6f545 v8::internal::RegExp::Exec(v8::internal::Isolate*, v8::internal::Handle<v8::internal::JSRegExp>, v8::internal::Handle<v8::internal::String>, int, v8::internal::Handle<v8::internal::RegExpMatchInfo>, v8::internal::RegExp::ExecQuirks) [/Applications/Visual Studio Code.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework]
12: 0x11ac6ec32 v8::internal::RegExp::Exec(v8::internal::Isolate*, v8::internal::Handle<v8::internal::JSRegExp>, v8::internal::Handle<v8::internal::String>, int, v8::internal::Handle<v8::internal::RegExpMatchInfo>, v8::internal::RegExp::ExecQuirks) [/Applications/Visual Studio Code.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework]
13: 0x11ac6d9a0 v8::internal::RegExp::Exec(v8::internal::Isolate*, v8::internal::Handle<v8::internal::JSRegExp>, v8::internal::Handle<v8::internal::String>, int, v8::internal::Handle<v8::internal::RegExpMatchInfo>, v8::internal::RegExp::ExecQuirks) [/Applications/Visual Studio Code.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework]
14: 0x11accb535 v8::internal::Runtime::DefineObjectOwnProperty(v8::internal::Isolate*, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, v8::internal::StoreOrigin, v8::Maybe<v8::internal::ShouldThrow>) [/Applications/Visual Studio Code.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework]
15: 0x3307f0a938
16: 0x3307f6fa95

rust-analyzer status (which gives a sense of the size of our workspace):

Workspaces:
Loaded 7525 packages across 1 workspace.

Analysis:
137mb of files
0b of index symbols (0)
8132 trees, 128 preserved
166433 trees, 128 preserved (Macros)
0b in total

File info:
Crate: CrateId(3744)
Dependencies: core=CrateId(0), alloc=CrateId(1), std=CrateId(4), http_uri_ext=CrateId(3123), async_generator=CrateId(3007), version=CrateId(3333), anyhow=CrateId(213), chrono=CrateId(126), ecdsa=CrateId(1052), futures=CrateId(192), hex=CrateId(312), http=CrateId(1261), hyper=CrateId(1274), log=CrateId(198), p256=CrateId(1055), pretty_assertions=CrateId(1298), rand=CrateId(211), serde=CrateId(117), serde_json=CrateId(118), serde_repr=CrateId(3337), sha2=CrateId(1054), signature=CrateId(1051), thiserror=CrateId(214), typed_builder=CrateId(3324), uuid=CrateId(162), assert_matches=CrateId(302), url=CrateId(346)
Crate: CrateId(3338)
Dependencies: core=CrateId(0), alloc=CrateId(1), std=CrateId(4), http_uri_ext=CrateId(3123), async_generator=CrateId(3007), version=CrateId(3333), anyhow=CrateId(213), chrono=CrateId(126), ecdsa=CrateId(1052), futures=CrateId(192), hex=CrateId(312), http=CrateId(1261), hyper=CrateId(1274), log=CrateId(198), p256=CrateId(1055), pretty_assertions=CrateId(1298), rand=CrateId(211), serde=CrateId(117), serde_json=CrateId(118), serde_repr=CrateId(3337), sha2=CrateId(1054), signature=CrateId(1051), thiserror=CrateId(214), typed_builder=CrateId(3324), uuid=CrateId(162)
Crate: CrateId(3743)
Dependencies: core=CrateId(0), alloc=CrateId(1), std=CrateId(4), http_uri_ext=CrateId(3122), async_generator=CrateId(3702), version=CrateId(1770), anyhow=CrateId(13), chrono=CrateId(38), ecdsa=CrateId(3741), futures=CrateId(242), hex=CrateId(1352), http=CrateId(1354), hyper=CrateId(1368), log=CrateId(248), p256=CrateId(3742), pretty_assertions=CrateId(1309), rand=CrateId(96), serde=CrateId(20), serde_json=CrateId(23), serde_repr=CrateId(3337), sha2=CrateId(1777), signature=CrateId(3740), thiserror=CrateId(25), typed_builder=CrateId(3324), uuid=CrateId(64)
Crate: CrateId(3745)
Dependencies: core=CrateId(0), alloc=CrateId(1), std=CrateId(4), http_uri_ext=CrateId(3122), async_generator=CrateId(3702), version=CrateId(1770), anyhow=CrateId(13), chrono=CrateId(38), ecdsa=CrateId(3741), futures=CrateId(242), hex=CrateId(1352), http=CrateId(1354), hyper=CrateId(1368), log=CrateId(248), p256=CrateId(3742), pretty_assertions=CrateId(1309), rand=CrateId(96), serde=CrateId(20), serde_json=CrateId(23), serde_repr=CrateId(3337), sha2=CrateId(1777), signature=CrateId(3740), thiserror=CrateId(25), typed_builder=CrateId(3324), uuid=CrateId(64), assert_matches=CrateId(889), url=CrateId(1387)

The language server itself is using about 2GB of memory, and when I have DevTools or the VSCode node.js debugger attached, I see about 450MB of heap usage.

If I run the bundled node directly, and run v8.getHeapStatistics(), it returns:

...
 heap_size_limit: 8640266240,
 ...

which does not line up with the levels seen above (which imply either a much smaller heap limit, or an attempt to allocate something too large to fit in the heap at all.

jonas-schievink commented 2 years ago

Hmm, the only place where I know we're allocating semi-persistent data is the "link shortener" added here: https://github.com/rust-lang/rust-analyzer/pull/12277/files

Does removing that code fix the issue?

woody77 commented 2 years ago

Something that I've noticed, which I think is a vscode issue (not RA), is that if you launch vscode itself with --max-memory=8192, while --max-old-space-size=8192 is passed to all of the locally-running node processes, it doesn't get passed to the remote server (verified in the vscode Help -> Process Explorer, where hovering over a process shows you the whole command line for it).

And then if you DO go off and edit (remote) <commit>/bin/code-server to pass --max-old-space-size to node when starting, it doesn't pass it to the (remote) extension host (but only the extension host):

\_ sh ~/.vscode-server/bin/<commit>/bin/code-server --start-server...
    \_ ~/.vscode-server/bin/<commit>/node --max-old-space-size=8192 ~/.vscode-server/bin/<commit>/out/server-main.js --start-server --host=127.0.0.1 ...
        \_ ~/.vscode-server/bin/<commit>/node --max-old-space-size=8192 ~/.vscode-server/bin/<commit>/out/bootstrap-fork --type=ptyHost
        |    \_ /bin/zsh
        \_ ~/.vscode-server/bin/<commit>/node /usr/local/google/home/aaronwood/.vscode-server/bin/<commit>/out/bootstrap-fork --type=extensionHost --transformURIs --useHostProxy=false
        \_ ~/.vscode-server/bin/<commit>/node --max-old-space-size=8192 ~/.vscode-server/bin/<commit>/out/bootstrap-fork --type=fileWatcher
woody77 commented 2 years ago

Hmm, the only place where I know we're allocating semi-persistent data is the "link shortener" added here: https://github.com/rust-lang/rust-analyzer/pull/12277/files

Does removing that code fix the issue?

I can try that locally (probably won't get to it for a few days).

However, I can confirm that this is prompted by changing files. Even files that aren't rust files (like our BUILD.gn files). And, we have a very large number of files in our source dir, (and without telling vscode otherwise, many more in our out/ dir).

woody77 commented 2 years ago

Further, from running locally with code --max-memory=8192, I haven't seen the issue, nor do I see whatever the usage of memory is, which makes me think that it's a very short-lived allocation of a lot of heap data. And whatever it is, it's changed recently (last few weeks).

woody77 commented 2 years ago

Further, from running locally with code --max-memory=8192, I haven't seen the issue, nor do I see whatever the usage of memory is, which makes me think that it's a very short-lived allocation of a lot of heap data. And whatever it is, it's changed recently (last few weeks).

And that continues to be the case. I'm using:

    "files.maxMemoryForLargeFilesMB": 8192

in my settings.json, which seems to have resolved the issue for LOCAL operation only (not with vscode-remote). It appears that how that config setting is handled with the remote server is different, and so it hasn't fully resolved the issue for me.

I can 100% reproduce the issue within seconds of opening a Rust file, I just need edit some other file. In which case it looks like it's some very-short-lived-but-very-large memory usage. In digging around, it seems that RegEx match results are a js closure, and therefore aren't GC'd in the same way, because they're compiled (and the prevalence of RegEx in the above stack traces is suspicious). Perhaps something using regex's on file paths in the extension as change notifications arrive?

woody77 commented 2 years ago

More investigation has led me to think that this is due to the extensionHost and file-watching.

I can consistently control whether or not I see the issue through use of the rust-analyzer.files.watcher configuration:

client -> crashes very consistently notify -> doesn't crash

After hacking up the extension js to log heap space stats, I see that when using client (the extension host itself?) for file watching notifications, that the code_space heap can grow by >30MB when I touch a single file (even non-rust file). I have ~2.7M files in my project dir (including my multiple cross-compilation outdirs).

This looks very much like it could be similar to: https://bugs.chromium.org/p/v8/issues/detail?id=10441

Which would make this an issue in the VSCode extension host, not in RA, but is provoked by RA registering for DidFileChange notifications.

woody77 commented 2 years ago

I've opened an issue with VSCode: https://github.com/microsoft/vscode/issues/153154

We'll see what happens there.