Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ cleanup() {
[ -z "$WORK_DIR" ] || rm -rf "$WORK_DIR"
}

enable_signal_traps() {
trap 'exit 130' INT
trap 'exit 143' TERM
}

require_https() {
# Args: <url> <label>
case "$1" in https://*) return 0 ;; esac
Expand Down Expand Up @@ -221,14 +226,26 @@ prepare_destination() {
mkdir -p "$FINAL_PARENT"

waited=0
until mkdir "$lock_dir" 2>/dev/null; do
while :; do
pending_signal_status=0
trap 'pending_signal_status=130' INT
trap 'pending_signal_status=143' TERM

# Defer cancellation until ownership is known. The subshell prevents a
# process-group signal from killing mkdir after it creates the lock.
if (trap '' INT TERM; CLOUDSMITH_CLI_LOCK_OWNER_PID=$$ mkdir "$lock_dir") 2>/dev/null; then
LOCK_DIR="$lock_dir"
enable_signal_traps
[ "$pending_signal_status" -eq 0 ] || exit "$pending_signal_status"
break
fi
enable_signal_traps
Comment thread
BartoszBlizniak marked this conversation as resolved.
[ "$pending_signal_status" -eq 0 ] || exit "$pending_signal_status"

[ "$waited" -lt 120 ] || die "timed out waiting for installation lock: $lock_dir"
sleep 1
waited=$((waited + 1))
done
# Track the lock for cleanup only once this process owns it, so a waiter
# that dies never releases another installer's lock.
LOCK_DIR="$lock_dir"
}

reuse_existing_install() {
Expand Down Expand Up @@ -373,8 +390,7 @@ main() {
}

trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
enable_signal_traps

parse_args "$@"
set_default_install_root
Expand Down
41 changes: 41 additions & 0 deletions tests/install.bats
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,47 @@ executable=$bin_dir/cloudsmith"
[ -d "$final_parent/.install.lock" ]
}

@test "a signal during lock acquisition exits without leaving a stale lock" {
build_fixture "$FIXTURE_DIR" "1.19.0" "$TEST_TARGET"
local final_parent="$INSTALL_ROOT/1.19.0/$TEST_TARGET"
local shim_dir="$BATS_TEST_TMPDIR/signal-mkdir"
local signal_marker="$BATS_TEST_TMPDIR/mkdir-sent-signal"
mkdir -p "$shim_dir"

cat > "$shim_dir/mkdir" <<'EOF'
#!/usr/bin/env bash
"$REAL_MKDIR" "$@"
Comment thread
Copilot marked this conversation as resolved.
status=$?
last_arg=
for arg do last_arg="$arg"; done
if [ "$status" -eq 0 ]; then
case "$last_arg" in
*/.install.lock)
: > "$MKDIR_SIGNAL_MARKER"
kill -TERM "$CLOUDSMITH_CLI_LOCK_OWNER_PID"
;;
esac
fi
exit "$status"
EOF
chmod +x "$shim_dir/mkdir"

run --separate-stderr env \
PATH="$shim_dir:$PATH" \
REAL_MKDIR="$(command -v mkdir)" \
MKDIR_SIGNAL_MARKER="$signal_marker" \
"$INSTALL_SH" \
--version 1.19.0 \
--target "$TEST_TARGET" \
--install-root "$INSTALL_ROOT" \
--manifest-url "$FIXTURE_URL/manifest.txt"

[ "$status" -eq 143 ]
[ -f "$signal_marker" ]
[ ! -e "$final_parent/.install.lock" ]
[ ! -e "$final_parent/cloudsmith/cloudsmith" ]
}

@test "concurrent installs of the same version both succeed" {
build_fixture "$FIXTURE_DIR" "1.19.0" "$TEST_TARGET"

Expand Down
Loading