Typescript: TSServer running out of memory for projects with large number of files

Created on 16 Apr 2019  ·  9Comments  ·  Source: microsoft/TypeScript

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.

Some Additional info

  • VSCode Version: 1.33.1
  • OS Version: Mac OS 10.14.5

Steps to Reproduce:

  1. Open project with large number of files
  2. Do some tasks until memory fills up

Output from code --status

Version:          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)
Needs More Info

Most helpful comment

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.

All 9 comments

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.

Was this page helpful?
0 / 5 - 0 ratings