mirror of
https://github.com/nvm-sh/nvm.git
synced 2026-08-25 00:00:22 +08:00
Two `nvm install <same version>` runs could race on the version directory - one removing or replacing it while the other reads or writes it. Take a per-version advisory lock (an atomically-created directory under $NVM_DIR/.cache locks) around the binary/source install, so a second run of the same version waits for the first; installs of different versions never contend. NVM_INSTALL_LOCK_TIMEOUT (seconds, default 600) bounds the wait, after which nvm reports the lock path so a lock left by a killed install can be removed. NVM_INSTALL_LOCK_STALE (minutes, default 0 / off) opts into automatically stealing a lock older than that, for unattended or CI use.
57 lines
2.5 KiB
Bash
Executable File
57 lines
2.5 KiB
Bash
Executable File
#!/bin/sh
|
|
|
|
cleanup() {
|
|
[ -n "${NVM_DIR}" ] && [ -d "${NVM_DIR}" ] && rm -rf "${NVM_DIR}"
|
|
unset -f die cleanup
|
|
unset NVM_DIR NVM_INSTALL_LOCK NVM_INSTALL_LOCK_TIMEOUT NVM_INSTALL_LOCK_STALE \
|
|
version lock OUTPUT EXIT_CODE
|
|
}
|
|
|
|
die() { echo "$@" ; cleanup ; exit 1; }
|
|
|
|
: nvm.sh
|
|
\. ../../../nvm.sh
|
|
|
|
type nvm_acquire_install_lock > /dev/null 2>&1 || die 'nvm_acquire_install_lock is not available'
|
|
|
|
NVM_DIR="$(mktemp -d)"
|
|
[ -n "${NVM_DIR}" ] || die 'unable to create temp NVM_DIR'
|
|
version='v20.0.0'
|
|
lock="$(nvm_cache_dir)/locks/$(nvm_install_lock_name "${version}")"
|
|
|
|
# An empty version is a no-op that succeeds and takes no lock.
|
|
unset NVM_INSTALL_LOCK
|
|
nvm_acquire_install_lock '' || die 'acquiring with an empty version should succeed as a no-op'
|
|
[ -z "${NVM_INSTALL_LOCK-}" ] || die 'an empty-version acquire should not record a lock'
|
|
|
|
# Acquiring a free lock succeeds, creates the lock dir, and records its path.
|
|
nvm_acquire_install_lock "${version}" || die 'acquiring a free lock should succeed'
|
|
[ -d "${lock}" ] || die 'acquire should create the lock directory'
|
|
[ "${NVM_INSTALL_LOCK}" = "${lock}" ] || die 'acquire should record NVM_INSTALL_LOCK'
|
|
|
|
# Release it so the following contention checks start from a held-by-someone-else state.
|
|
command rmdir "${lock}" 2>/dev/null
|
|
unset NVM_INSTALL_LOCK
|
|
|
|
# A lock held by "another process", with no wait budget, fails and names the path.
|
|
command mkdir -p "${lock}"
|
|
OUTPUT="$(NVM_INSTALL_LOCK_TIMEOUT=0 nvm_acquire_install_lock "${version}" 2>&1)"; EXIT_CODE=$?
|
|
[ "${EXIT_CODE}" != "0" ] || die 'acquire should fail when the lock is held and the timeout is 0'
|
|
case "${OUTPUT}" in
|
|
*"${lock}"*) ;;
|
|
*) die "the timeout message should name the lock path; got >${OUTPUT}<" ;;
|
|
esac
|
|
[ -d "${lock}" ] || die 'a failed acquire must not remove the held lock'
|
|
|
|
# By default, a held lock is never stolen no matter how old it is.
|
|
touch -t 202001010000 "${lock}" 2>/dev/null || die 'unable to age the lock dir'
|
|
NVM_INSTALL_LOCK_TIMEOUT=0 nvm_acquire_install_lock "${version}" 2>/dev/null && die 'an old lock must not be stolen unless NVM_INSTALL_LOCK_STALE is set'
|
|
[ -d "${lock}" ] || die 'the old lock should still be held'
|
|
|
|
# With NVM_INSTALL_LOCK_STALE set, a sufficiently old lock is stolen and re-acquired.
|
|
unset NVM_INSTALL_LOCK
|
|
NVM_INSTALL_LOCK_STALE=1 NVM_INSTALL_LOCK_TIMEOUT=0 nvm_acquire_install_lock "${version}" || die 'a stale lock should be stolen when NVM_INSTALL_LOCK_STALE is set'
|
|
[ "${NVM_INSTALL_LOCK}" = "${lock}" ] || die 'stealing a stale lock should acquire it'
|
|
|
|
cleanup
|