Skip to content

[ci] Make Android SDK test setup downloads more resilient#12001

Open
simonrozsival wants to merge 1 commit into
dotnet:mainfrom
simonrozsival:dev/simonrozsival/ci-sdk-download-robustness
Open

[ci] Make Android SDK test setup downloads more resilient#12001
simonrozsival wants to merge 1 commit into
dotnet:mainfrom
simonrozsival:dev/simonrozsival/ci-sdk-download-robustness

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Jul 7, 2026

Copy link
Copy Markdown
Member

Summary

This is another incremental hardening PR for the recurring install OpenJDK and accept Android SDK licenses CI failure where large Android SDK archives from dl.google.com fail with:

MSB3923: Failed to download file "https://dl.google.com/android/repository/...".
The response ended prematurely. (ResponseEnded)

The specific recent failure already had the newer retry/backoff behavior: the log shows retries waiting 30s, 60s, 90s, and 120s before ultimately exhausting x86_64-29_r08-darwin.zip. So this PR does not replace the existing download retry work; it builds on it and covers the next failure mode.

Prior work this builds on

  • #11348 moved OpenJDK installation into an MSBuild NoTargets project.
  • #11440 moved Android SDK/NDK component downloads into src/androidsdk/androidsdk.targets, which is what this setup step builds.
  • #11618 added the shared android-archives pipeline cache for build jobs and bumped the built-in DownloadFile retries.
  • #11647 added the shared DownloadFileWithRetry wrapper because ResponseEnded is a top-level HttpIOException that MSBuild's built-in DownloadFile retry logic does not catch.
  • #11693 made DownloadOneFileWithRetry skip cleanly on no-op builds using per-file stamps, which makes whole-step retries less wasteful once some archives already succeeded.
  • #11817 added escalating outer backoff between retry attempts, so transient CDN hiccups get several minutes to recover instead of burning through attempts immediately.

Remaining gap

Those PRs made individual downloads much more resilient, but the failing path is the test environment setup template, not the main build template:

  • build-{linux,macos,windows}-steps.yaml already uses cache-android-archives.yaml.
  • setup-test-environment-steps.yaml did not use that cache before running src/androidsdk/androidsdk.csproj.
  • The install OpenJDK and accept Android SDK licenses run-dotnet-preview.yaml invocation also had retryCountOnTaskFailure: 0 through the template default.

That means a macOS test setup job could still cold-download several large archives concurrently and fail the entire job if one archive exhausted the per-file retry/backoff loop.

Change

This PR hardens that remaining test-setup path by:

  • adding a condition parameter to cache-android-archives.yaml, preserving the existing default behavior for build jobs
  • invoking the Android archive cache from setup-test-environment-steps.yaml for macOS test setup before SDK downloads run
  • setting retryCountOnTaskFailure: 2 on the install OpenJDK and accept Android SDK licenses step

The cache is intentionally limited to macOS test setup for now. Linux archive caching already has a separate disk-pressure concern tracked by #11837, so this PR avoids expanding Linux cache usage while still targeting the observed macOS ResponseEnded failure.

Why step retry helps

The per-file retry wrapper is still the first line of defense. The task-level retry is a second line of defense for the case where a specific archive exhausts all per-file attempts.

On a second task attempt, successfully downloaded archives should be reused/skipped by the existing cache/stamp logic, so the retry mostly focuses on the archive that failed rather than restarting all work from scratch.

Validation

  • git diff --check

Add a conditional hook to the Android archive cache template and use it in test-environment setup for macOS, where the OpenJDK/Android SDK license step has been exhausting retries on large Google SDK archives.

Also retry the OpenJDK/Android SDK license setup task so a fully exhausted per-file download attempt can be recovered by a fresh step attempt while preserving already-downloaded archives.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 7, 2026 09:18

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 hardens the Azure Pipelines Android test-environment setup against transient download failures (notably macOS ResponseEnded when fetching large Android SDK/OpenJDK archives) by restoring an archive cache earlier and adding an extra end-to-end retry around the SDK setup step.

Changes:

  • Invoke the Android toolchain archive cache during macOS test-environment setup (before OpenJDK/SDK downloads) to reduce reliance on live downloads.
  • Make cache-android-archives.yaml conditionally runnable via a new condition template parameter.
  • Retry the “install OpenJDK and accept Android SDK licenses” step (and workload pack extraction) to allow recovery after exhausting per-file download retries.
Show a summary per file
File Description
build-tools/automation/yaml-templates/setup-test-environment-steps.yaml Restores Android archive cache on macOS before SDK setup; adds task retries for resilient setup.
build-tools/automation/yaml-templates/cache-android-archives.yaml Adds a condition parameter and applies it to the variable-setup steps and Cache@2 step.

Copilot's findings

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

@simonrozsival

Copy link
Copy Markdown
Member Author

/review

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

Android PR Reviewer completed successfully!

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🤖 ✅ LGTM — Well-reasoned, conservative CI hardening. I formed an independent assessment of the mechanics before reading the description, and they hold up:

  • The cache actually takes effect on this path. The template restores/saves at $(ANDROID_ARCHIVES_DIR) = $HOME/android-archives, which matches the MSBuild default AndroidToolchainCacheDirectory ($(HOME)\android-archives in Configuration.props). The following androidsdk.csproj run downloads into that same directory with no explicit override — identical to how build-macos-steps.yaml / build-linux-steps.yaml already consume this template. ✔
  • Template expansion is correct. With the caller passing condition: and(succeeded(), eq(agent.os, 'Darwin')), the Windows pwsh variable step resolves to and(...Darwin..., eq(Agent.OS,'Windows_NT')) = false (correctly skipped on macOS), while the non-Windows variable step and the Cache@2 task correctly run. ✔
  • Scope is correctly limited to macOS. On Linux/Windows test setup the condition is false, so the cache is a no-op there — consistent with the stated deferral of Linux caching (#11837). ✔
  • retryCountOnTaskFailure: 2 is forwarded correctly by run-dotnet-preview.yaml (param exists, default 0), and the retried work is idempotent (license acceptance + hash-verified downloads; already-fetched archives skip via cache/stamps). It matches the existing extract workload packs retry two steps below. ✔
  • CI is green. All 42 dotnet-android checks (including the macOS test jobs that now exercise this path) plus license/cla passed. mergeable_state: blocked is due to the pending required review from jonathanpeppers, not CI.

Two minor 💡 suggestions left inline (document the new condition parameter; a benign succeeded() behavior change for existing build callers). Neither blocks merge.

Issues: ❌ 0 · ⚠️ 0 · 💡 2

Generated by Android PR Reviewer for #12001 · 181.6 AIC · ⌖ 20.2 AIC · ⊞ 6.8K
Comment /review to run again


parameters:
xaSourcePath: $(System.DefaultWorkingDirectory)/android
condition: succeeded()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🤖 💡 Documentation — The new condition parameter gates every step in this shared template (both prepare ... cache variables steps and the Cache@2 task), but the otherwise-thorough header comment doesn't mention it. A one-line note that callers can pass a condition to scope the whole cache to specific agents — as setup-test-environment-steps.yaml now does for Darwin — would help future maintainers understand why the parameter exists.

Rule: Document shared-template parameters

- script: echo "##vso[task.setvariable variable=ANDROID_ARCHIVES_DIR]$HOME/android-archives"
displayName: prepare android-archives cache variables
condition: ne(variables['Agent.OS'], 'Windows_NT')
condition: and(${{ parameters.condition }}, ne(variables['Agent.OS'], 'Windows_NT'))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🤖 💡 Patterns — For existing callers that don't pass condition (the three build-*-steps.yaml templates), this step's guard changes from ne(Agent.OS, 'Windows_NT') to and(succeeded(), ne(Agent.OS, 'Windows_NT')) — it now also requires prior steps to have succeeded (same for the Windows pwsh step below). This is almost certainly harmless — setting the cache-dir variable after an earlier failure has no value, and it aligns these steps with the Cache@2 task's implicit succeeded() default — but it's a behavior change for build jobs, so worth a quick confirm that it's intended.

Rule: Watch shared-template edits for side effects on existing callers

@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 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

flaky-tests 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