Skip to content

Commit 13a79d0

Browse files
committed
test(pm): stress release cache race in gha
1 parent c8b2ac9 commit 13a79d0

1 file changed

Lines changed: 193 additions & 23 deletions

File tree

.github/workflows/downloader-race-repro.yml

Lines changed: 193 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,51 +2,221 @@ name: downloader-race-repro
22

33
on:
44
workflow_dispatch:
5+
inputs:
6+
release_tag:
7+
description: "utoo release tag to test"
8+
required: false
9+
default: "utoo-v1.1.4"
10+
attempts:
11+
description: "stress attempts"
12+
required: false
13+
default: "40"
14+
parallelism:
15+
description: "parallel utoo install processes"
16+
required: false
17+
default: "24"
518
pull_request:
619
paths:
720
- ".github/workflows/downloader-race-repro.yml"
8-
- "crates/pm/src/util/downloader.rs"
921

1022
permissions:
1123
contents: read
1224

1325
jobs:
14-
linux-repro:
15-
name: linux downloader race repro
26+
linux-release-repro:
27+
name: linux release cache race repro
1628
runs-on: ubuntu-latest
17-
timeout-minutes: 45
29+
timeout-minutes: 60
30+
env:
31+
UTOO_RELEASE_TAG: "utoo-v1.1.4"
32+
ATTEMPTS: "40"
33+
PARALLELISM: "24"
34+
REGISTRY: "https://registry.npmjs.org"
35+
CACHE_DIR: ${{ runner.temp }}/utoo-npm-cache
36+
LOG_DIR: ${{ runner.temp }}/utoo-repro-logs
1837
steps:
19-
- uses: actions/checkout@v4
38+
- name: Apply manual inputs
39+
if: ${{ github.event_name == 'workflow_dispatch' }}
40+
shell: bash
41+
run: |
42+
set -euo pipefail
2043
21-
- name: Install Rust
22-
uses: dtolnay/rust-toolchain@stable
23-
with:
24-
toolchain: nightly-2026-01-04
44+
echo "UTOO_RELEASE_TAG=${{ github.event.inputs.release_tag }}" >> "$GITHUB_ENV"
45+
echo "ATTEMPTS=${{ github.event.inputs.attempts }}" >> "$GITHUB_ENV"
46+
echo "PARALLELISM=${{ github.event.inputs.parallelism }}" >> "$GITHUB_ENV"
2547
26-
- name: Cache cargo
27-
uses: Swatinem/rust-cache@v2
28-
with:
29-
shared-key: downloader-race-repro-linux
48+
- name: Download utoo release
49+
shell: bash
50+
env:
51+
GH_TOKEN: ${{ github.token }}
52+
run: |
53+
set -euo pipefail
54+
55+
mkdir -p "$RUNNER_TEMP/utoo-release" "$LOG_DIR"
56+
gh release download "$UTOO_RELEASE_TAG" \
57+
--repo "$GITHUB_REPOSITORY" \
58+
--pattern "utoo-linux-x64.tar.gz" \
59+
--dir "$RUNNER_TEMP/utoo-release"
60+
61+
tar -xzf "$RUNNER_TEMP/utoo-release/utoo-linux-x64.tar.gz" -C "$RUNNER_TEMP/utoo-release"
62+
chmod +x "$RUNNER_TEMP/utoo-release/utoo"
63+
echo "$RUNNER_TEMP/utoo-release" >> "$GITHUB_PATH"
64+
"$RUNNER_TEMP/utoo-release/utoo" --version
3065
31-
- name: Try to reproduce zero-length resolved cache
66+
- name: Create repro project template
67+
shell: bash
68+
run: |
69+
set -euo pipefail
70+
71+
mkdir -p "$RUNNER_TEMP/utoo-repro-template"
72+
cat > "$RUNNER_TEMP/utoo-repro-template/package.json" <<'JSON'
73+
{
74+
"name": "utoo-cache-race-repro",
75+
"version": "0.0.0",
76+
"private": true,
77+
"dependencies": {
78+
"@babel/plugin-transform-react-jsx-source": "7.29.7",
79+
"cookie-es": "3.1.1",
80+
"d3-time-format": "4.1.0",
81+
"date-fns": "4.4.0",
82+
"recharts-scale": "0.4.5",
83+
"tapable": "2.3.3"
84+
}
85+
}
86+
JSON
87+
88+
- name: Stress shared cache with release utoo
3289
shell: bash
3390
run: |
3491
set -uo pipefail
3592
36-
test_name='downloader::tests::repro_download_can_leave_zero_file_if_racing_process_dies_after_resolved'
37-
for attempt in $(seq 1 30); do
38-
echo "::group::attempt ${attempt}"
39-
cargo test -p utoo-pm "${test_name}" -- --ignored --nocapture
40-
status=$?
93+
UTOO_BIN="$(command -v utoo)"
94+
ZERO_LIST="$RUNNER_TEMP/zero-package-json.txt"
95+
: > "$ZERO_LIST"
96+
97+
scan_zero_package_json() {
98+
find "$CACHE_DIR" -type f -path '*/package/package.json' -size 0c -print 2>/dev/null | sort
99+
}
100+
101+
dump_cache_summary() {
102+
local attempt="$1"
103+
local mode="$2"
104+
{
105+
echo "attempt=$attempt mode=$mode"
106+
echo "cache_dir=$CACHE_DIR"
107+
find "$CACHE_DIR" -maxdepth 4 -type f \( -name package.json -o -name _resolved \) \
108+
-printf '%s %p\n' 2>/dev/null | sort || true
109+
} > "$LOG_DIR/cache-summary-${mode}-${attempt}.txt"
110+
}
111+
112+
start_installers() {
113+
local attempt="$1"
114+
local mode="$2"
115+
local workdir="$RUNNER_TEMP/repro-${mode}-${attempt}"
116+
mkdir -p "$workdir"
117+
118+
PIDS=()
119+
for idx in $(seq 1 "$PARALLELISM"); do
120+
local project="$workdir/project-$idx"
121+
mkdir -p "$project"
122+
cp "$RUNNER_TEMP/utoo-repro-template/package.json" "$project/package.json"
123+
(
124+
cd "$project"
125+
UTOO_CACHE_DIR="$CACHE_DIR" UTOO_REGISTRY="$REGISTRY" \
126+
timeout --kill-after=5s 180s "$UTOO_BIN" \
127+
--cache-dir "$CACHE_DIR" \
128+
--registry "$REGISTRY" \
129+
install --ignore-scripts \
130+
> "$LOG_DIR/install-${mode}-${attempt}-${idx}.log" 2>&1
131+
) &
132+
PIDS+=("$!")
133+
done
134+
}
135+
136+
wait_installers() {
137+
local status=0
138+
for pid in "${PIDS[@]}"; do
139+
if ! wait "$pid"; then
140+
status=1
141+
fi
142+
done
143+
return "$status"
144+
}
145+
146+
kill_after_resolved() {
147+
local deadline=$((SECONDS + 120))
148+
while [ "$SECONDS" -lt "$deadline" ]; do
149+
if find "$CACHE_DIR" -type f -name _resolved -print -quit 2>/dev/null | grep -q .; then
150+
sleep 0.02
151+
local killed=0
152+
for pid in "${PIDS[@]}"; do
153+
if kill -0 "$pid" 2>/dev/null; then
154+
kill -9 "$pid" 2>/dev/null || true
155+
killed=$((killed + 1))
156+
if [ "$killed" -ge $((PARALLELISM / 2)) ]; then
157+
break
158+
fi
159+
fi
160+
done
161+
echo "killed $killed installers after _resolved appeared"
162+
return 0
163+
fi
164+
sleep 0.01
165+
done
166+
echo "no _resolved observed before kill deadline"
167+
}
168+
169+
run_attempt() {
170+
local attempt="$1"
171+
local mode="$2"
172+
173+
rm -rf "$CACHE_DIR"
174+
mkdir -p "$CACHE_DIR"
175+
176+
echo "::group::attempt $attempt ($mode)"
177+
start_installers "$attempt" "$mode"
178+
if [ "$mode" = "kill-after-resolved" ]; then
179+
kill_after_resolved
180+
fi
181+
182+
wait_installers || true
183+
scan_zero_package_json | tee "$ZERO_LIST"
184+
dump_cache_summary "$attempt" "$mode"
41185
echo "::endgroup::"
42186
43-
if [ "${status}" -eq 0 ]; then
44-
echo "::notice::Reproduced zero-length package.json in resolved downloader cache on attempt ${attempt}."
187+
if [ -s "$ZERO_LIST" ]; then
188+
echo "::error::Reproduced zero-byte cache package.json with release $UTOO_RELEASE_TAG on attempt $attempt ($mode)."
189+
cat "$ZERO_LIST"
190+
return 0
191+
fi
192+
193+
return 1
194+
}
195+
196+
echo "utoo=$UTOO_BIN"
197+
echo "release=$UTOO_RELEASE_TAG attempts=$ATTEMPTS parallelism=$PARALLELISM registry=$REGISTRY"
198+
199+
for attempt in $(seq 1 "$ATTEMPTS"); do
200+
if run_attempt "$attempt" "passive"; then
201+
exit 0
202+
fi
203+
204+
if run_attempt "$attempt" "kill-after-resolved"; then
45205
exit 0
46206
fi
47207
48-
echo "attempt ${attempt} did not hit the zero-length window"
208+
echo "attempt $attempt did not leave a zero-byte package.json"
49209
done
50210
51-
echo "::error::Did not reproduce zero-length package.json after 30 attempts."
211+
echo "::error::Did not reproduce a zero-byte cache package.json after $ATTEMPTS release-based attempts."
52212
exit 1
213+
214+
- name: Upload repro logs
215+
if: always()
216+
uses: actions/upload-artifact@v4
217+
with:
218+
name: utoo-release-cache-race-logs
219+
path: |
220+
${{ runner.temp }}/utoo-repro-logs
221+
${{ runner.temp }}/zero-package-json.txt
222+
if-no-files-found: ignore

0 commit comments

Comments
 (0)