Over the past month or so, myself and my team have been experiencing an extreme slowdown in VSCode, to the point of being nearly unusable.
I suspect we have passed some threshold in our project where the number files in memory simply hit the upper bounds of memory allocated to TSServer, we are experiencing exactly what is described in https://github.com/Microsoft/TypeScript/issues/18055
There are even times when the electron_node tsserver.js process appears stuck in a non-productive garbage collection loop, with CPU usage for that process hovering around 300% indefinitely, and memory stuck at an unchanging 2081 MB
I have tried running VSCode without extensions, as well as the insiders version and still face the same issues.
I have also tried building VSCode from source and passing --max_old_space_size=4096 to the TSServer process but this had no effect, and I suspect this issue is related to https://github.com/electron/electron/issues/9693
If anyone has found a solution to this issue (I don't care how complicated/involved it is) please share. At this point, this issue has become a huge impediment for the entire team. Also please let me know if any additional info would be helpful in resolving this issue.
Steps to Reproduce:
code --statusVersion: Code 1.33.1 (51b0b28134d51361cf996d2f0a1c698247aeabd8, 2019-04-11T08:22:55.268Z)
OS Version: Darwin x64 18.6.0
CPUs: Intel(R) Core(TM) i7-7820HQ CPU @ 2.90GHz (8 x 2900)
Memory (System): 16.00GB (0.03GB free)
Load (avg): 6, 8, 8
VM: 0%
Screen Reader: no
Process Argv:
GPU Status: 2d_canvas: disabled_software
checker_imaging: disabled_off
flash_3d: disabled_software
flash_stage3d: disabled_software
flash_stage3d_baseline: disabled_software
gpu_compositing: disabled_software
multiple_raster_threads: enabled_on
native_gpu_memory_buffers: enabled
rasterization: disabled_software
surface_synchronization: enabled_on
video_decode: disabled_software
webgl: disabled_off
webgl2: disabled_off
CPU % Mem MB PID Process
11 98 37339 code main
12 66 37340 gpu-process
28 279 37341 window (locations.tsx — ui)
0 147 37625 extensionHost
0 33 37628 /Applications/Visual Studio Code.app/Contents/Frameworks/Code Helper.app/Contents/MacOS/Code Helper /Applications/Visual Studio Code.app/Contents/Resources/app/extensions/json-language-features/server/dist/jsonServerMain --node-ipc --clientProcessId=37625
216 2081 42197 electron_node tsserver.js
0 16 42200 electron_node typingsInstaller.js typesMap.js
0 33 37626 watcherService
0 66 37637 searchService
0 66 37627 shared-process
2 82 37935 window (Process Explorer)
Workspace Stats:
| Window (locations.tsx — ui)
| Folder (ui): 10061 files
| File types: html(6153) tsx(1770) js(500) map(392) gz(328) xml(257)
| ts(208) scss(189) json(108) css(81)
| Conf files: package.json(9) tsconfig.json(1) tslint.json(1)
| launch.json(1) settings.json(1) webpack.config.js(1)
Moving upstream to get input from the TypeScript team
@azmenak we forked vscode to launch node with more RAM here: https://github.com/lucidsoftware/vscode/commit/c1151606d1ea5d2fd51577f3df1c53ab547ae080
Notice that doing it this way assumes the computer has node installed since it's not using the node packaged with electron. (A pretty safe assumption, however, one that vs code can not reasonably make, making a change like this unmergeable.)
I'm actually getting tired of updating the fork, so I've been contemplating a new idea. Using vscode's Tsdk setting to point to a modified version of tsserver, which launches a separate node instance with more memory and acts as a proxy between vscode and the real tsserver.
@danielscottjames thanks for the update, changing fork to spawn was the part missing for us.
Modifying Tsdk is a very interesting idea, seems much easier to maintain than a whole version of VSCode. Going to spend some time with the engineering team today to see if we can make this work
If you guys get that working, mind sharing the solution here?
After some fiddling around, we has some preliminary success by renaming tsserver.js to _tsserver.js and swapping the original file with this implementation:
const { spawn } = require("child_process");
const path = require("path");
const nextArgv = process.argv.slice(2);
nextArgv.unshift(path.resolve(__dirname, "./_tsserver.js"));
nextArgv.push("--max_old_space_size=4096");
spawn("node", nextArgv, {
cwd: process.cwd(),
env: process.env,
stdio: "inherit"
});
Still have to work out a decent way to package this as right now we just modified the files directly in the lib folder of our Typescript fork.
For now things seem to have improved, will play around with VSCode for the next few days to see if any issues come up.
I could be wrong but I think node requires the memory flag to be before the filename, so, unshift instead of push there.
Probably not the most elegant approach, but I've published this
https://github.com/azmenak/tsserver-bridge
Basically just a script to copy TS from your node_modules to another folder, and replaces the tsserver.js with the bridge. We run this on postinstall so hopefully no more maintenance required.
We are testing it internally now
Closing this issue as we have a workaround. Thanks to @danielscottjames for the idea of creating the tsserver bridge.
Thanks for creating the proxy script! The approach has been working well for us too, there haven't been any unexpected issues so far.
Most helpful comment
After some fiddling around, we has some preliminary success by renaming
tsserver.jsto_tsserver.jsand swapping the original file with this implementation:Still have to work out a decent way to package this as right now we just modified the files directly in the
libfolder of our Typescript fork.For now things seem to have improved, will play around with VSCode for the next few days to see if any issues come up.