Closed
Description
Hi!
Thanks for working on the typescript go project. I'm having an issue with the LSP. When i start it together with Neovim it causes Neovim to freeze and rapidly consume all memory available and crash.
I have tried installing via npm and building tsgo from source, both causes the same behaviour.
Before the preview was released I was able to build tsgo from source and run tsgo with neovim without problems so I'm guessing it's a recent change.
Below is a screenshot of neovim running with the tsgo lsp. The top process is the nvim process which spawned tsgo.
I'm starting the lsp with this command:
tsgo --lsp --stdio
Any help to resolve this would be greatly appreciated! If you need any more information from me please reach out!
Metadata
Metadata
Assignees
Labels
No labels
Activity
jakebailey commentedon May 27, 2025
If you are able to build and reproduce it, it would be helpful if you could bisect the problem (since we don't have any other info to go on from this).
redsuperbat commentedon May 27, 2025
Thanks for a quick answer. I'll see what I can get out of bisecting.
Is there any way to toggle verbose logging from the LSP? My initial guess is that neovim somehow get's overloaded with diagnostics or something else which would consume a lot of memory.
jakebailey commentedon May 27, 2025
If you want to hack it locally, just replace
os.Stderr
in the LSP main function withio.Discard
.redsuperbat commentedon May 27, 2025
Alright after a quick bisect I was able to determine that this commit: #806 is the bad one.
Unfortunately it's not 100% clear to me what the issue might be since it's quite the big commit. I will have a look and see if anything stands out...
EDIT:
By adding this
To the lsp server options I was able to confirm that tsgo sends a humungous amount of these globPattern payloads:
So I'm assuming it has something to do with the
FileSystemWatcher
🤔jakebailey commentedon May 27, 2025
@andrewbranch
sheetalkamat commentedon May 27, 2025
This is because of watching all failed lookup locations as is instead of directories for it.
jakebailey commentedon May 27, 2025
Yeah, I think it's causing VS Code to freeze sometimes too:
issacgerges commentedon May 27, 2025
Was just about to report this issue, I wonder if the issue is actually responding with the registrationOptions.watchers. In our repo (Slack) this response exceeds the body size limit (it gets to something like 1.3gb) and kills the server.
Each resolution (even if there's a file at that path, and even if theres some sort of ambient type declared like
declare module *.pdf
) adds watchers forindex.js
,index.jsx
,index.ts
,index.tsx
,package.json
,{file}.d.ts
This repro is just a simple script that generates 10k 0-byte pdfs and adds an import for each. 10k apparently isn't enough but I assume if we go high enough we'd hit the same crash
An example
redsuperbat commentedon May 28, 2025
What makes this problem interesting is that it seems like the LSP is telling the client to watch files in the
node_modules
folder. This seems a bit weird since those files are unlikely to change during development. Perhaps the globbing should exclude the files in the.gitignore
if it exists and folders likenode_modules
by default?Watching files in
node_modules
also explains why neovim crashes since node_modules tends to be thousands of javascript/typescript files.EDIT:
Looking back at this, the
kind: 1
indicates it's listening forcreated
files, which would be fine for node_modules if the goal is to populate the type environment. However it seems like the glob pattern is expanded on the server side before sending it to the client. Perhaps that's the issue?cartond commentedon May 28, 2025
I made this repro repo. There are instructions on the
README
to generate the files to cause the crash.From the testing on there, 20k files wasn't nearly enough, but 1M files reproduces the bug consistently (
Exception has occurred: Error: Cannot create a string longer than 0x1fffffe8 characters
). I double checked the consistency by having @issacgerges try this on his machine.@redsuperbat in our (@slackhq) case, it wasn't just the
node_modules
. It was that an imported files (e.g.import icon_1 from '../files/img/icon_1.png';
) would be treated as a module, so the module resolver made watcher globs for non-module files (icon_1.png
in this example). The resulting stream looks something like this.cartond commentedon May 28, 2025
@redsuperbat I spoke too soon, and only looked into the first ~5k bytes of the buffer which was files like
.pdf
. Sorry!When the file does resolve, the watcher still includes all the files that don't exist now, but could be created and change responses. For example
The resolver (as mentioned above) then adds all the failed resolutions (
globPattern
) to watch:when it knows
thing.tsx
does exist. I understand a change to any of those files would change how the lsp works. Was this how the non-go version of tsc worked?Is there an option to exclude this overhead?
Alternatively, if the file is deleted, then add that list of options back in to watch.