Skip to content

Commit cef5a6a

Browse files
authored
fix: avoid ln race condition in bootstrap script (#3797)
Avoid startup failures when multiple processes attempt to create the same symlinks simultaneously. Ensure symlinks are created and ignore errors if they already exist, which can happen in a race condition during startup.
1 parent a9de4d5 commit cef5a6a

2 files changed

Lines changed: 30 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ END_UNRELEASED_TEMPLATE
7272

7373
{#v0-0-0-fixed}
7474
### Fixed
75+
* (bootstrap) Fixed a potential race condition with symlink creation during
76+
startup.
7577
* (gazelle) Fixed handling of auto-included `__init__.py` files when generating `py_binary`
7678
targets ([#3729](https://github.com/bazel-contrib/rules_python/issues/3729)).
7779
* (entry_point) From now on `mypy` type checking will be skipped on the generated

python/private/stage1_bootstrap_template.sh

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,30 @@ if [[ -n "${RULES_PYTHON_BOOTSTRAP_VERBOSE:-}" ]]; then
66
set -x
77
fi
88

9+
# Creates a symlink. If the symlink already exists, it is tolerated to avoid
10+
# race conditions during startup.
11+
function _symlink() {
12+
local target="$1"
13+
local link="$2"
14+
if ln -s "$target" "$link" 2>/dev/null; then
15+
return 0
16+
fi
17+
# If it failed, maybe it already exists because of a race.
18+
if [[ -L "$link" || -e "$link" ]]; then
19+
return 0
20+
fi
21+
# If it doesn't exist, maybe we don't have write permission in the directory.
22+
local dir
23+
dir=$(dirname "$link")
24+
if [[ ! -w "$dir" ]]; then
25+
echo >&2 "ERROR: Cannot create symlink $link: Directory $dir is not writable"
26+
else
27+
echo >&2 "ERROR: Failed to create symlink $link -> $target"
28+
fi
29+
return 1
30+
}
31+
32+
933
# runfiles-root-relative path
1034
STAGE2_BOOTSTRAP="%stage2_bootstrap%"
1135

@@ -153,7 +177,7 @@ if [[ "$IS_ZIPFILE" == "1" ]]; then
153177
fi
154178
# The bin/ directory may not exist if it is empty.
155179
mkdir -p "$(dirname $python_exe)"
156-
ln -s "$symlink_to" "$python_exe"
180+
_symlink "$symlink_to" "$python_exe"
157181
elif [[ "$RECREATE_VENV_AT_RUNTIME" == "1" ]]; then
158182
if [[ -n "$RULES_PYTHON_EXTRACT_ROOT" ]]; then
159183
use_exec=1
@@ -226,16 +250,16 @@ EOF
226250
fi
227251

228252
mkdir -p "$venv/bin"
229-
ln -s "$python_exe_actual" "$python_exe"
253+
_symlink "$python_exe_actual" "$python_exe"
230254

231255
if [[ ! -e "$venv_site_packages" ]]; then
232256
mkdir -p $(dirname $venv_site_packages)
233-
ln -s "$runfiles_venv_site_packages" "$venv_site_packages"
257+
_symlink "$runfiles_venv_site_packages" "$venv_site_packages"
234258
fi
235259
fi
236260

237261
if [[ ! -e "$venv/pyvenv.cfg" ]]; then
238-
ln -s "$runfiles_venv/pyvenv.cfg" "$venv/pyvenv.cfg"
262+
_symlink "$runfiles_venv/pyvenv.cfg" "$venv/pyvenv.cfg"
239263
fi
240264
else
241265
use_exec=1

0 commit comments

Comments
 (0)