← all posts

Stop VSCode from smashing your machine

Under some circumstances VSCode/VSCodium can end up consuming a humongous amount of resources of your system.

As far as I know, this is not a common scenario; otherwise, I think, more people would be talking about it and sharing recipes to fix the problem. In any case, however improbable it is, it happened to me, and here I am telling you about it.

For the impatient, I’ll drop the “solution” here, with some commentary. I’ll leave the longer explanation for later, for those who still have some extra curiosity left in them.

Our .vscode/settings.json file should have something similar to this:

{
    // Tells VSCode to not "watch" the inodes of those directories or
    // files. For people developing in Rust, this could end up taking
    // huge amounts of their precious RAM.
    "files.watcherExclude": {
        // For people using NodeJS
        "node_modules/**": true,
        "**/node_modules/**": true,
        // For people developing in Rust
        "target/**": true,
        "**/target/**": true,
        // A custom example: My Flatpak build cache
        "packages/linux/flatpak/.cache": true,
        "packages/linux/flatpak/.cache/**": true,
    },

    // Tells VSCode to not search in these directories/files.
    "files.exclude": {
        // Default settings
        "**/.git": true,
        "**/.svn": true,
        "**/.hg": true,
        "**/.DS_Store": true,
        "**/Thumbs.db": true,

        // My custom stuff
        "**/node_modules": true,
        "**/target": true,
        "packages/linux/flatpak/.cache": true
    },

    // Prunes git-ignored files from the searches (ripgrep: rg process)
    "search.useIgnoreFiles": true,
    // As before, but uses the global .gitignore too
    "search.useGlobalIgnoreFiles": true,
    // It turns out that we must really insist to ensure that the
    // previous 2 options really apply
    "search.experimental.useIgnoreFilesInFindFiles": true,

    // For most people this won't be relevant, but if, for some reason,
    // you have symlinks to files/directories outside of your
    // repository, then this becomes very, very important!
    "search.followSymlinks": false,
}

Part 1: The ripgrep fiasco

The easiest to spot problem, but also the least likely to happen to you, was how rg (ripgrep, used by VSCode and VSCodium) was smashing my whole system. Identifying that process as problematic was trivial, as I could see it in the Gnome Resources app. The surprised arrived when I used lsof -p [PID] to see what files rg was reading: everything in /sys and /run… recursively, non-stop.

So, how could something like this happen? Well, when I “architected” the build process for “my” Slicer’s Flatpak packages I made the unfortunate decision of placing everything within the confines of the repository’s directory.

The flatpak build process is somehow convoluted, but suffice to say that some of the cache directories end up having symlinks to directories such as /run. Without the settings that I’ve shown before, ripgrep will follow those symbolic links wherever they go, and there we have it, total disaster.

For this one there are a couple of simple solutions that work well:

  1. forbiding ripgrep from following symlinks (line 43 of the settings.json file I shared).
  2. excluding whole directories from the ripgrep search (lines 18-30).

My recommendation is to apply both, it will save you from unwanted surprises.

Part 2: the inotify fiasco

Automated searches are not the only culprit behind VSCode’s voracity. One of its mose useful features is to detect files changes almost immediately, this is done through syscalls such as inotify, that put a “watcher” on those files.

In my case, working on a relatively big Rust project, and having a target/ directory using over 100GiB , this feature was causing havoc. But I’m getting ahead of myself, as I didn’t know this when I started investigating.

Some searches through the Internet lead me into looking for processes “abusing” inotify. A command to list a relation between processes and “watched inodes”:

lsfd -n -o PID,COMMAND,NAME -Q 'TYPE == "inotify"'

It returns us something like this (but much longer):

 50753 biome           inodes=2258@tmpfs,61865985@dm-1,2@dm-1
 50909 codium          inodes=2258@tmpfs,61865985@dm-1,2@dm-1
 59149 python          inodes=2258@tmpfs,61865985@dm-1,2@dm-1
106824 codium          inodes=2258@tmpfs,61865985@dm-1,2@dm-1
109495 codium          inodes=2258@tmpfs,61865985@dm-1,2@dm-1
110831 gvfsd-recent    inodes=57409545@dm-1
179542 mattermost-desk inodes=134@tmpfs,136@tmpfs,42365774@dm-1,42217187@dm-1,42366012@dm-1,59377999@dm-1,42215890@dm-1,42365908@dm-1
179542 mattermost-desk inodes=10641276@dm-1,198@tmpfs,36@tmpfs,2@tmpfs
179622 mattermost-desk inodes=198@tmpfs,36@tmpfs,2@tmpfs

Now, we can apply a filter to select for the processes watching over a directory:

lsfd -n -o PID,COMMAND,NAME -Q 'TYPE == "inotify"' | grep -E "[=,]$(stat -c %i ./target)@"

And we can get a result like this one:

 50418 codium          inodes=58740042@dm-1,58615332@dm-1,59018718@dm-1,58982471@dm-1,59018430@dm-1,58615343@dm-1,59018696@dm-1,58615452@dm-1,60435918@dm-1,59247791@dm-1,59018720@dm-1,59247822@dm-1,59247850@dm-1,59247856@dm-1,59247848@dm-1,59244973@dm-1,58615385@dm-1,59247824@dm-1,59247811@dm-1,59247799@dm-1,60432158@dm-1,59247868@dm-1,59247807@dm-1,59247805@dm-1,58615349@dm-1,59247793@dm-1,59247813@dm-1,59247797@dm-1,59018714@dm-1,59247864@dm-1,59926714@dm-1,59247858@dm-1,59018713@dm-1,59018761@dm-1,59247819@dm-1,58982472@dm-1,59018400@dm-1,59247838@dm-1,59018757@dm-1,60031285@dm-1,59247834@dm-1,59018758@dm-1,59926715@dm-1,58615308@dm-1,59018759@dm-1,59018429@dm-1,59018691@dm-1,59247844@dm-1,60435940@dm-1,59247826@dm-1,59018749@dm-1,59018743@dm-1,59018693@dm-1,59018744@dm-1,58615329@dm-1,59018733@dm-1,59018707@dm-1,59018729@dm-1,59018700@dm-1,59018694@dm-1,59018735@dm-1,59018715@dm-1,59247832@dm-1,60031286@dm-1,59018717@dm-1,59018750@dm-1,59018725@dm-1,59018698@dm-1,58615370@dm-1,59018708@dm-1,58982504@dm-1,59018768@dm-1,59018690@dm-1,58982485@dm-1,59247830@dm-1,59018728@dm-1,59018765@dm-1,59018405@dm-1,59018746@dm-1,58615447@dm-1,59018397@dm-1,58615339@dm-1,58615404@dm-1,58740044@dm-1,58615425@dm-1,58615434@dm-1,59019759@dm-1,58615426@dm-1,58615432@dm-1,60435939@dm-1,58615456@dm-1,60031288@dm-1,58615513@dm-1,58615368@dm-1,60435938@dm-1,58615421@dm-1,59513996@dm-1,60435934@dm-1,58615358@dm-1

which shows us that a process codium is “watching” over ./target, among many other directories (what I’m showing here is a minified recreation, my case was much worse, but I didn’t document it at the time).

Once we know this, if we suspect it can be a problem (as I did), we can toggle the settings I’ve shown at the beginning of this article, restart the IDE, and see if the inotify watcher is still there… Of course, we can also check how much is our system suffering from overutilization (again, in my case, things got much better after that specific change).

And… that’s it! Thank you for reading me 🤓.