Skip to content

Commit 5f2828b

Browse files
authored
fix(installer): time out stalled runtime downloads (#108619)
* fix(installer): bound curl download stalls * chore: format installer timeout tests * fix(installer): limit timeout to true download stalls * fix(installer): scope timeout to transfer stalls
1 parent d8b723f commit 5f2828b

4 files changed

Lines changed: 52 additions & 2 deletions

File tree

scripts/install-cli.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,11 @@ download_file() {
137137
detect_downloader
138138
fi
139139
if [[ "$DOWNLOADER" == "curl" ]]; then
140-
curl -fsSL --proto '=https' --tlsv1.2 --retry 3 --retry-delay 1 --retry-connrefused -o "$output" "$url"
140+
# Bound post-connect stalls without imposing a total download duration.
141+
curl -fsSL --proto '=https' --tlsv1.2 \
142+
--speed-limit 1 --speed-time 30 \
143+
--retry 3 --retry-delay 1 --retry-connrefused \
144+
-o "$output" "$url"
141145
return
142146
fi
143147
wget -q --https-only --secure-protocol=TLSv1_2 --tries=3 --timeout=20 -O "$output" "$url"

scripts/install.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,11 @@ download_file() {
114114
detect_downloader
115115
fi
116116
if [[ "$DOWNLOADER" == "curl" ]]; then
117-
curl -fsSL --proto '=https' --tlsv1.2 --retry 3 --retry-delay 1 --retry-connrefused -o "$output" "$url"
117+
# Bound post-connect stalls without imposing a total download duration.
118+
curl -fsSL --proto '=https' --tlsv1.2 \
119+
--speed-limit 1 --speed-time 30 \
120+
--retry 3 --retry-delay 1 --retry-connrefused \
121+
-o "$output" "$url"
118122
return
119123
fi
120124
wget -q --https-only --secure-protocol=TLSv1_2 --tries=3 --timeout=20 -O "$output" "$url"

test/scripts/install-cli.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,27 @@ function linkRequiredShellTools(bin: string) {
4141
describe("install-cli.sh", () => {
4242
const script = readFileSync(SCRIPT_PATH, "utf8");
4343

44+
it("bounds stalled curl downloads and propagates timeout failures", () => {
45+
const result = runInstallCliShell(`
46+
set -euo pipefail
47+
source "${SCRIPT_PATH}"
48+
curl() {
49+
printf 'curl=%s\n' "$*"
50+
return 28
51+
}
52+
DOWNLOADER=curl
53+
set +e
54+
download_file "https://example.invalid/node.tar.gz" "/tmp/node.tar.gz"
55+
printf 'status=%s\n' "$?"
56+
`);
57+
58+
expect(result.status).toBe(0);
59+
expect(result.stdout).toContain("--speed-limit 1 --speed-time 30");
60+
expect(result.stdout).not.toContain("--connect-timeout");
61+
expect(result.stdout).toContain("--retry 3 --retry-delay 1 --retry-connrefused");
62+
expect(result.stdout).toContain("status=28");
63+
});
64+
4465
it("does not clean an unrelated legacy checkout during the default npm install", () => {
4566
const main = script.slice(script.indexOf("\nmain() {"));
4667
expect(main).not.toContain("cleanup_legacy_submodules");

test/scripts/install-sh.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,27 @@ describe("install.sh", () => {
8282
}
8383
});
8484

85+
it("bounds stalled curl downloads and propagates timeout failures", () => {
86+
const result = runInstallShell(`
87+
set -euo pipefail
88+
source "${SCRIPT_PATH}"
89+
curl() {
90+
printf 'curl=%s\n' "$*"
91+
return 28
92+
}
93+
DOWNLOADER=curl
94+
set +e
95+
download_file "https://example.invalid/archive.tgz" "/tmp/archive.tgz"
96+
printf 'status=%s\n' "$?"
97+
`);
98+
99+
expect(result.status).toBe(0);
100+
expect(result.stdout).toContain("--speed-limit 1 --speed-time 30");
101+
expect(result.stdout).not.toContain("--connect-timeout");
102+
expect(result.stdout).toContain("--retry 3 --retry-delay 1 --retry-connrefused");
103+
expect(result.stdout).toContain("status=28");
104+
});
105+
85106
it("runs apt-get through noninteractive wrappers", () => {
86107
expect(script).toContain("apt_get()");
87108
expect(script).toContain('DEBIAN_FRONTEND="${DEBIAN_FRONTEND:-noninteractive}"');

0 commit comments

Comments
 (0)