Skip to content

[flaky-ci] Isolate Gradle state in CI - #12707

Open
simonrozsival wants to merge 9 commits into
mainfrom
simonrozsival-gradle-ci-state-isolation
Open

simonrozsival wants to merge 9 commits into
mainfrom
simonrozsival-gradle-ci-state-isolation

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Sep 8, 2026

Copy link
Copy Markdown
Member

Summary

  • serialize java-source-utils before manifestmerger, extending the existing Gradle project chain so their single-use daemons cannot race over one registry
  • place pipeline GRADLE_USER_HOME under $(Agent.TempDirectory) and persist only caches/modules-2 plus wrapper distributions
  • recover from an incomplete Azure cache restore with an empty job-local cache, without retrying Gradle compile or test failures
  • keep recovered optional cache failures non-gating through a current-job timeline allowlist while still failing on every unrelated issue
  • preserve high-importance Gradle output for the previously opaque Xamarin.Android.LibraryProjectZip-LibBinding failure

Root cause

The nine Windows incidents in builds 1574227, 1574359, 1575505, 1576382, 1576423, 1576487, 1576567, 1578693, and 1578694 all failed while manifestmerger and java-source-utils ran concurrently. Although both use --no-daemon, Gradle starts a single-use daemon and both processes shared C:\Users\cloudtest\.gradle\daemon\9.5.0. The failing stack ends in daemon registry removal and file-lock contention handling with Timeout waiting to connect to the Gradle daemon.

Build 1575255 has a separate macOS Kotlin ClasspathEntrySnapshotTransform/KotlinCompilerVersion failure and is intentionally not covered by a generic retry. Build 1576677 did not retain Gradle stdout/stderr, so this change improves that target's diagnostics without assuming the same cause.

References #12704.

Optional cache recovery contract

Cache tasks have stable task reference names. Recovery reads cache state through missing-safe environment variables and adds a task reference to the semicolon-delimited RECOVERED_OPTIONAL_TASK_REFS variable only after the affected cache path has been reset to a verified empty directory.

The final issue gate has the stable failOnUnexpectedIssues task reference and polls the current Azure build timeline within a wall-clock bound until its own record is visible and every preceding direct task under SYSTEM_JOBID is terminal with a documented result. Timeline HTTP requests use HttpClient.SendAsync(..., ResponseContentRead, cancellationToken) with the remaining budget, so response-body publication time is included and no overdue response—including the first—is accepted. The gate then permits only failed or SucceededWithIssues records whose task reference/name appears in the runtime allowlist; the gate and later/post-job records are excluded. Missing tokens, missing or malformed timeline data, API failures, unsupported task results, incomplete publication at timeout, absent allowlists, and unrelated issue tasks all fail closed.

Validation

  • PowerShell fixtures cover unset cache state, successful and failed cache reset, recovered cache-only timeline results, unrelated later issues, absent allowlists, task-name fallback, timeline API request shape, missing tokens, timeline/API failures, and unsupported task results
  • timeline publication fixtures cover an in-progress preceding task that later fails, later succeeds, or never becomes terminal, plus delayed gate-record publication
  • a local HTTP regression sends response headers immediately and delays a complete valid timeline body beyond the deadline; the full-response read is canceled and the gate fails within the tolerance
  • parsed the changed PowerShell scripts, Azure YAML, and MSBuild XML files
  • checked the gate against a real Azure timeline containing both a failed cache task and an unrelated failed artifact task
  • verified the Mono.Android static graph reuses one java-source-utils project instance through the new manifestmerger dependency
  • built java-source-utils with a cold isolated Gradle home and reran it warm
  • built manifestmerger twice with the same isolated Gradle home; both Gradle invocations completed successfully
  • verified the binding project preprocesses with high-importance Gradle stdout and stderr

Prevent concurrent single-use Gradle daemons from sharing the manifestmerger/java-source-utils registry, keep pipeline Gradle state job-local, and persist only dependency downloads and wrapper distributions. Preserve high-importance Gradle output for the previously opaque binding fixture failure.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival added the flaky-ci Intermittent CI failures and work to improve CI reliability label Sep 8, 2026
simonrozsival and others added 3 commits September 8, 2026 13:06
Use missing-safe cache state recovery and inspect the current Azure job timeline so only explicitly recovered optional cache failures remain non-gating.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Poll the current-job timeline within a wall-clock bound before applying recovered optional-task exemptions, so delayed task results cannot be masked.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use HttpClient full-response cancellation against the remaining wall-clock budget and reject overdue or unknown timeline results.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@simonrozsival

Copy link
Copy Markdown
Member Author

/review

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

Generated by Android PR Reviewer for #12707

@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.

❌ Reject

Found 2 errors:

  • Nested issue gates do not preserve the reusable templates’ custom xaSourcePath, causing script-not-found failures for non-default checkouts.
  • The new HTTP integration test expects Azure log masking in raw loopback request bytes and therefore fails when run.

CI is green for Azure DevOps build 1587136. The Gradle cache isolation and fail-closed timeline logic are otherwise thoughtfully defensive, with good coverage of delayed and malformed timeline responses.

Generated by Android PR Reviewer for #12707 · gpt56 · 200.8 AIC · ⌖ 19.5 AIC · ⊞ 25.7K
Comment /review to run again

Comment thread build-tools/automation/yaml-templates/fail-on-issue.yaml Outdated
Comment thread build-tools/automation/scripts/FailOnUnexpectedIssues.Tests.ps1 Outdated
Preserve custom checkout paths in nested test templates and parse the actual authorization header in the loopback fixture.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@simonrozsival

Copy link
Copy Markdown
Member Author

/review

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

Generated by Android PR Reviewer for #12707

@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.

⚠️ Needs Changes

Findings: 0 errors, 0 warnings, 1 suggestion.

The Gradle state isolation, cache-recovery allowlist, timeline gate, and project-reference serialization are coherent with the surrounding build graph. I left one inline suggestion to cover the explicit non-success HTTP response path.

CI is currently red: build 1587818 failed in the Linux Build, macOS Build, and Windows Build & Smoke Test lanes. The available check metadata does not expose enough Azure log detail to attribute those failures to this change, but the PR is not merge-ready until those lanes are green.

Generated by Android PR Reviewer for #12707 · gpt56 · 240.9 AIC · ⌖ 8.84 AIC · ⊞ 25.7K
Comment /review to run again

Comment thread build-tools/automation/scripts/FailOnUnexpectedIssues.ps1
simonrozsival and others added 2 commits September 9, 2026 11:09
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Exercise the explicit non-success response path in the fail-closed Azure timeline gate fixture.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsival marked this pull request as ready for review September 10, 2026 10:41
Copilot AI lite review requested due to automatic review settings September 10, 2026 10:41

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.

Copilot review overview

🟡 Changes recommended

The newly added PowerShell test fixtures for the gate/recovery logic are not currently wired to run in CI, reducing confidence that future edits won’t reintroduce failures.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Lite
Findings: 1 Medium severity · 2 Low severity

New issues introduced by this change (3)
Severity Finding
Medium severity build-tools/​automation/​scripts/​FailOnUnexpectedIssues.Tests.ps1⚠️ These PowerShell test fixtures don’t appear to be executed anywhere in the pipeline (no YAML…
Low severity build-tools/​automation/​yaml-templates/​cache-gradle.yaml — 💡 Similar to fail-on-issue.yaml, this direct path concatenation can be fragile if xaSourcePath
Low severity build-tools/​automation/​yaml-templates/​fail-on-issue.yaml — 💡 Using string concatenation with .../${{ parameters.xaSourcePath }}/... is a bit brittle across…
What changed in this PR

This PR aims to reduce CI flakiness by isolating Gradle state per job (while still caching the “download-only” bits) and by adding a stricter, timeline-based gate that only allows known-recovered optional cache failures to remain non-gating.

Changes:

  • Isolates GRADLE_USER_HOME under $(Agent.TempDirectory) and caches only caches/modules-2 plus wrapper distributions, with recovery when cache restore is incomplete.
  • Serializes Gradle-wrapper–invoking projects by extending the project reference chain (preventing concurrent single-use daemons from racing).
  • Improves diagnostics by preserving high-importance Gradle stdout/stderr for the binding zip test target and by replacing the simple “SucceededWithIssues => fail” gate with a timeline-aware allowlist gate.
File Description
tests/​CodeGen-Binding/​Xamarin.Android.LibraryProjectZip-LibBinding/​Xamarin.Android.LibraryProjectZip-LibBinding.targets Elevates Gradle stdout/stderr importance to improve failure diagnostics.
src/​manifestmerger/​manifestmerger.csproj Adds a project reference to serialize Gradle wrapper usage with java-source-utils.
build-tools/​automation/​yaml-templates/​variables.yaml Sets job-level GRADLE_USER_HOME default under $(Agent.TempDirectory).
build-tools/​automation/​yaml-templates/​stage-msbuild-emulator-tests.yaml Plumbs xaSourcePath through to the issue gate template.
build-tools/​automation/​yaml-templates/​run-msbuild-tests.yaml Plumbs xaSourcePath through to the issue gate template.
build-tools/​automation/​yaml-templates/​fail-on-issue.yaml Replaces inline gating with FailOnUnexpectedIssues.ps1 and provides OAuth token env.
build-tools/​automation/​yaml-templates/​cache-gradle.yaml Creates isolated Gradle home, caches only download artifacts, and invokes recovery validation.
build-tools/​automation/​yaml-templates/​build-linux-steps.yaml Updates cleanup commentary to reflect new Gradle home location.
build-tools/​automation/​scripts/​RecoverGradleCache.ps1 Resets cache dirs to a verified-empty state when cache restore state is missing/invalid.
build-tools/​automation/​scripts/​RecoverGradleCache.Tests.ps1 Adds fixtures validating cache-reset and allowlist emission behavior.
build-tools/​automation/​scripts/​FailOnUnexpectedIssues.ps1 Implements timeline-based gate to fail closed unless only allowlisted recovered tasks are in issue state.
build-tools/​automation/​scripts/​FailOnUnexpectedIssues.Tests.ps1 Adds fixtures validating timeline polling, allowlisting, and fail-closed behavior.
build-tools/​automation/​azure-pipelines.yaml Passes xaSourcePath to the gate template for correct script resolution.
build-tools/​automation/​azure-pipelines-public.yaml Passes xaSourcePath to the gate template for correct script resolution.
build-tools/​automation/​azure-pipelines-internal.yaml Passes xaSourcePath to the gate template for correct script resolution.

Comment thread build-tools/automation/scripts/FailOnUnexpectedIssues.Tests.ps1
Comment thread build-tools/automation/yaml-templates/cache-gradle.yaml Outdated
Comment thread build-tools/automation/yaml-templates/fail-on-issue.yaml Outdated
simonrozsival and others added 2 commits September 10, 2026 15:11
Execute the PowerShell regression fixtures once in the Linux build and resolve script paths with Join-Path across platforms.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

flaky-ci Intermittent CI failures and work to improve CI reliability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants