nvm
nvm copied to clipboard
`nvm uninstall` slowed down by (sometimes infinite) recursion into symlinked directories
The issue
nvm uninstall
, specifically the nvm_check_file_permissions
helper, is unnecessarily slowed down by recursing into symlinked directories inside the global node_modules. In rare cases, it can get into infinite recursion.
Context
npm link ../related-module
can be used to test changes in another module, without needing to repeatedly publish or pack on each change. For some reason, instead of a direct symlink, NPM creates 2 layers of symlinks - from the original repo's node_modules/related-module
to the global node_modules, then from the global node_modules to the destination directory.
nvm_check_file_permissions
follows the symlink from the global node_modules directory and recurses into the destination tree, which can take a long time if there are a lot of symlinks or large symlinked directories.
Also, if the symlink destination happens to itself have a circular symlink somewhere, nvm_check_file_permissions
goes into infinite recursion, which is how I discovered this issue. 😓
Proposal
I believe nvm_check_file_permissions
shouldn't need to follow symlinks, as all that matters is whether the current user has permission remove the symlinks in the global node_modules directory.
Changing the directory check to skip symlinks with && [ ! -L "$FILE" ]
worked for me to resolve the infinite recursion and let the uninstall finish. With the change, a symlink will go to the else
case, which checks that the current user can delete it.
Anything I'm missing?
I could create a PR, but it'd likely just be the source code change for now, since I'm not sure I'll have time to look at writing a good test for it this week or next.
Minimal repro
Pick an unused Node version for the fresh install. Here, I picked a non-LTS non-latest version, as it's less likely to be in use.
Setup
nvm install v19.8.1
cd $(npm root -g)
ln -s . im-a-circular-symlink
Run ls -al
to see the symlink in the global node_modules.
To attempt the deletion:
nvm use default
NVM_DEBUG=1 nvm uninstall v19.8.1
Watch the infinite recursion for a while, Ctrl+C to stop.
More practical repro
This demonstrates it's possible to get into the situation under a more normal scenario.
First, cd to a directory you clone Git repos to.
nvm install v19.8.1
git clone https://github.com/share/sharedb.git
cd sharedb/examples/rich-text
npm link ../..
nvm use default
NVM_DEBUG=1 nvm uninstall v19.8.1
Someone would run npm link ../..
to try the example with the local code, instead of with the package installed off NPM.
Run ls -al $(npm root -g)
to see the symlink in global node_modules that was created by npm link
.
Issue template
Operating system and version:
MacOS 13.3.1 (22E261)
nvm debug
output:
nvm --version: v0.39.3
$TERM_PROGRAM: Apple_Terminal
$SHELL: /bin/bash
$SHLVL: 1
whoami: 'ehwang'
${HOME}: /Users/ehwang
${NVM_DIR}: '${HOME}/.nvm'
${PATH}: ${NVM_DIR}/versions/node/v18.16.0/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:${HOME}/.npm/bin:${HOME}/go/bin:${HOME}/Library/Python/3.10/bin
$PREFIX: ''
${NPM_CONFIG_PREFIX}: ''
$NVM_NODEJS_ORG_MIRROR: ''
$NVM_IOJS_ORG_MIRROR: ''
shell version: 'GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin22)'
uname -a: 'Darwin 22.4.0 Darwin Kernel Version 22.4.0: Mon Mar 6 21:00:17 PST 2023; root:xnu-8796.101.5~3/RELEASE_X86_64 x86_64'
checksum binary: 'shasum'
OS version: macOS 13.3.1 22E261
awk: /usr/bin/awk, awk version 20200816
curl: /usr/bin/curl, curl 7.87.0 (x86_64-apple-darwin22.0) libcurl/7.87.0 (SecureTransport) LibreSSL/3.3.6 zlib/1.2.11 nghttp2/1.51.0
wget: not found
sed: /usr/bin/sed
cut: /usr/bin/cut
basename: /usr/bin/basename
rm: /bin/rm
mkdir: /bin/mkdir
xargs: /usr/bin/xargs
git: /usr/bin/git, git version 2.39.2 (Apple Git-143)
grep: /usr/bin/grep, grep (BSD grep, GNU compatible) 2.6.0-FreeBSD
nvm current: v18.16.0
which node: ${NVM_DIR}/versions/node/v18.16.0/bin/node
which iojs:
which npm: ${NVM_DIR}/versions/node/v18.16.0/bin/npm
npm config get prefix: ${NVM_DIR}/versions/node/v18.16.0
npm root -g: ${NVM_DIR}/versions/node/v18.16.0/lib/node_modules
nvm ls
output:
v14.19.3
v16.20.0
v18.14.0
-> v18.16.0
default -> v18 (-> v18.16.0)
iojs -> N/A (default)
unstable -> N/A (default)
node -> stable (-> v18.16.0) (default)
stable -> 18.16 (-> v18.16.0) (default)
lts/* -> lts/hydrogen (-> v18.16.0)
lts/argon -> v4.9.1 (-> N/A)
lts/boron -> v6.17.1 (-> N/A)
lts/carbon -> v8.17.0 (-> N/A)
lts/dubnium -> v10.24.1 (-> N/A)
lts/erbium -> v12.22.12 (-> N/A)
lts/fermium -> v14.21.3 (-> N/A)
lts/gallium -> v16.20.0
lts/hydrogen -> v18.16.0
How did you install nvm
?
Install script in README
What steps did you perform?
Ran nvm uninstall v14.19.3
What happened?
Command hangs for over an hour before I stopped it to investigate
What did you expect to happen?
The Node version to get uninstalled in a reasonable amount of time
Is there anything in any of your profile files that modifies the PATH
?
Yes. Just one for Python and Go binaries, irrelevant to this.
That sounds great, and makes a lot of sense - I see the slowdown myself but I also have a lot of linked global modules.
Any chance you'd be up for a PR that includes a test case with such a symlink cycle?
For anyone reaching this via Google wondering how to uninstall when it's hanging indefinitely: a workaround for now is to wipe the node_modules
and then run the nvm uninstall
command.