Skip to content

[ci] Retry .NET SDK provisioning with exponential backoff#12120

Closed
simonrozsival wants to merge 2 commits into
dotnet:mainfrom
simonrozsival:dev/simonrozsival/retry-dotnet-install
Closed

[ci] Retry .NET SDK provisioning with exponential backoff#12120
simonrozsival wants to merge 2 commits into
dotnet:mainfrom
simonrozsival:dev/simonrozsival/retry-dotnet-install

Conversation

@simonrozsival

Copy link
Copy Markdown
Member

Problem

The install .NET SDK to bin//dotnet pipeline step intermittently fails with transient CDN network errors, e.g.:

curl: (56) Recv failure: Connection reset by peer
##[error]Bash exited with code '56'.

Example: https://dev.azure.com/dnceng-public/public/_build/results?buildId=1508469

There was no retry/backoff around the download or install, so a single dropped connection to the .NET CDN failed the whole job.

Fix

Add retry-with-exponential-backoff (5 attempts, 2s → 4s → 8s → 16s) around both network operations in the provisioning scripts — the dotnet-install script download and the SDK install itself (which also downloads from the CDN):

  • eng/install-dotnet.sh: new retry() helper wraps the dotnet-install.sh download and the SDK install invocation. curl also gains --retry 5 --retry-delay 1 --retry-all-errors (so it retries connection resets on its own) and an EXIT trap to clean up the temp file.
  • eng/install-dotnet.ps1: new Invoke-WithRetry helper wraps Invoke-WebRequest and the install-script invocation, which now throws on a non-zero exit code so retries actually trigger.

Testing

  • bash -n and pwsh syntax checks pass on both scripts.
  • Unit-tested the bash retry helper: succeeds when the command eventually passes, gives up after 5 attempts with a non-zero exit otherwise.

Copilot AI review requested due to automatic review settings July 15, 2026 12:25
The "install .NET SDK to bin/<Configuration>/dotnet" pipeline step
intermittently fails with transient CDN network errors such as
"curl: (56) Recv failure: Connection reset by peer".

Add retry-with-exponential-backoff (5 attempts, 2s/4s/8s/16s) around
both network operations in the provisioning scripts:

- eng/install-dotnet.sh: new retry() helper wraps the dotnet-install.sh
  download and the SDK install invocation; curl also gains
  --retry 5 --retry-delay 1 --retry-all-errors and an EXIT trap to
  clean up the temp file.
- eng/install-dotnet.ps1: new Invoke-WithRetry helper wraps
  Invoke-WebRequest and the install-script invocation (now throwing on
  non-zero exit so retries trigger).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 85e98740-084d-4e54-9512-ea3cf95a1436
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/retry-dotnet-install branch from 5745544 to d47d26c Compare July 15, 2026 12:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to harden CI/local provisioning of the repo-local .NET SDK (bin/<Configuration>/dotnet) against transient network/CDN failures by adding retry logic with exponential backoff around both the dotnet-install script download and the SDK install step.

Changes:

  • Add an exponential-backoff retry helper to the Bash provisioning script and wrap both network operations.
  • Add an exponential-backoff retry helper to the PowerShell provisioning script and ensure non-zero exit codes trigger retries.
Show a summary per file
File Description
eng/install-dotnet.sh Adds retry() helper and wraps curl download + dotnet-install.sh invocation with exponential backoff.
eng/install-dotnet.ps1 Adds Invoke-WithRetry helper and wraps Invoke-WebRequest + dotnet-install.ps1 invocation with exponential backoff + explicit non-zero exit handling.

Copilot's findings

  • Files reviewed: 2/2 changed files
  • Comments generated: 3

Comment thread eng/install-dotnet.sh Outdated
Comment thread eng/install-dotnet.ps1 Outdated
Comment thread eng/install-dotnet.sh
- eng/install-dotnet.sh: drop curl's own --retry/--retry-all-errors
  flags (--retry-all-errors isn't supported by the older system curl on
  some macOS images); the outer retry() helper already backs off on any
  failure, including transport errors like "curl: (56)".
- eng/install-dotnet.ps1: only pass -UseBasicParsing on Windows
  PowerShell (Desktop); on PowerShell 7+ (Core) it's unnecessary.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 85e98740-084d-4e54-9512-ea3cf95a1436
@simonrozsival

Copy link
Copy Markdown
Member Author

Addressed the review findings in a follow-up commit:

  1. curl --retry-all-errors portability (install-dotnet.sh): Removed curl's own --retry/--retry-delay/--retry-all-errors flags. --retry-all-errors isn't supported by the older system curl on some macOS images. The outer retry() helper already backs off on any failure, including transport errors like curl: (56) Recv failure, so nothing is lost.

  2. Invoke-WebRequest -UseBasicParsing on PowerShell 7+ (install-dotnet.ps1): Now edition-gated — -UseBasicParsing is only passed on Windows PowerShell (Desktop, 5.1) where it's needed to avoid the IE engine; it's omitted on PowerShell 7+ (Core) where basic parsing is the only mode.

  3. Large unrelated deletions: That was a bad diff from the initial push (a --no-checkout worktree produced a commit whose tree contained only the 2 files). The branch was rebuilt on origin/main and force-pushed before this review round — the PR now correctly shows only the 2 intended files changed.

@jonathanpeppers

Copy link
Copy Markdown
Member

/review

@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Code Review — ✅ LGTM (with minor suggestions)

Wraps both network operations in eng/install-dotnet.sh and eng/install-dotnet.ps1 with a 5-attempt exponential-backoff retry to absorb transient .NET CDN failures. The implementation is correct and well-commented.

Verified:

  • Bashretry() composes safely with set -euo pipefail (the if "$@" guard suppresses errexit per-attempt), the curl -o tmp + atomic mv cache pattern is preserved, and the EXIT trap is correctly scoped and cleared after success. Removing curl's own --retry-all-errors (unsupported on older macOS curl) in favor of the outer retry() is reasonable.
  • PowerShellInvoke-WithRetry rethrows after $MaxAttempts and the install-script wrapper converts a non-zero $LASTEXITCODE into a throw so retries actually trigger. Edition-gating -UseBasicParsing (Desktop only) is correct. Backoff schedule (2s→4s→8s→16s) matches the comments.
  • CI — All 44 dotnet-android checks are green.

Suggestions (non-blocking): 2 💡 inline comments — retry scope for non-transient failures, and PowerShell try-block indentation.

Nice, focused reliability fix. 👍

Issues: ❌ 0 · ⚠️ 0 · 💡 2

Generated by Android PR Reviewer for #12120 · 77.3 AIC · ⌖ 13 AIC · ⊞ 6.8K
Comment /review to run again

Comment thread eng/install-dotnet.sh

echo "Installing .NET SDK $sdk_version into $install_dir"
bash "$install_script" --version "$sdk_version" --install-dir "$install_dir" --no-path
retry bash "$install_script" --version "$sdk_version" --install-dir "$install_dir" --no-path

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 💡 Patternsretry() retries on any non-zero exit, so a genuinely deterministic failure (bad SDK version → curl -f HTTP 404/exit 22, or dotnet-install.sh rejecting an invalid --version) now burns all 5 attempts and ~30s of backoff before surfacing. That's an acceptable tradeoff for a provisioning script, but consider narrowing retries to transient transport errors (or at least noting in the header comment that non-transient failures are also retried) so future readers aren't surprised by the delay on a hard failure.

(Rule: Retry scope — non-transient failures)

Comment thread eng/install-dotnet.ps1
# -UseBasicParsing is required on Windows PowerShell (Desktop) to avoid
# the Internet Explorer engine; on PowerShell 7+ (Core) it is the only
# mode and the parameter is unnecessary, so gate it by edition.
$iwrArgs = @{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 💡 Formatting — The body inside this try { ... } block (lines 67–79) is indented one level shallower than the surrounding try/finally (compare Move-Item on the next block, which is at 4 spaces). Re-indenting the $iwrArgs hashtable and Invoke-WithRetry block to match keeps the try body visually nested. Cosmetic only.

(Rule: Consistent indentation)

@simonrozsival simonrozsival added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label Jul 15, 2026
@simonrozsival simonrozsival closed this by deleting the head repository Jul 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants