[Xamarin.Android.Build.Tasks] Fix _CreateAar race under parallel build - #11527
Conversation
Fixes: #11514 Under parallel build (`-m`), library projects with `<GeneratePackageOnBuild>true</GeneratePackageOnBuild>` could fail with: error XARLP7024: System.IO.IOException: The process cannot access the file '.../<assembly>.aar' because it is being used by another process. at Microsoft.Android.Build.Tasks.Files.HashFile(...) at Xamarin.Android.Tasks.ResolveLibraryProjectImports.Extract(...) `_CreateAar` was being scheduled up to *three* times for a single library build, on two distinct MSBuild project instances: * Once from `BuildDependsOn`. * Once from `_UpdateAndroidResourcesDependsOn` on the regular `Build` chain. * Once more from `_UpdateAndroidResourcesDependsOn` on a Pack-dispatched project instance entered via `_GetFrameworkAssemblyReferences`. Because that instance has different global properties (NuGet pack's per-TFM inner-target dispatch), MSBuild does not dedupe it and may run it on a fresh worker node, opening a write/write or write/read race against consumers that read the `.aar` via `Files.HashFile`. `_UpdateAndroidResources` is the aapt2/resource-designer step; producing the publish artifact (`.aar`) is unrelated. The AAR is still produced because `_CreateAar` remains in `BuildDependsOn` for non-application projects, and Pack depends on `Build`, so a normal build and `dotnet pack` both still create it. Removing `_CreateAar` from `_UpdateAndroidResourcesDependsOn` collapses the count to a single invocation per build, eliminating the race window and the redundant AAR rewrites. Added a regression test in `IncrementalBuildTest` that builds a library with `GeneratePackageOnBuild=true` at `LoggerVerbosity.Detailed` and asserts that `Target "_CreateAar" in file ...` appears exactly once in the build log. Verified against the repro project on the issue: the count drops from 3 to 1 with the fix applied. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Removes _CreateAar from _UpdateAndroidResourcesDependsOn to prevent it from being invoked multiple times during a single build (notably via NuGet pack's per-TFM inner-target dispatch), which was causing a write/read race on the .aar file under parallel builds (XARLP7024). _CreateAar is still wired through BuildDependsOn for non-application projects, so dotnet build and dotnet pack continue to produce the AAR.
Changes:
- Remove
_CreateAarfrom_UpdateAndroidResourcesDependsOninMicrosoft.Android.Sdk.BuildOrder.targets. - Add
IncrementalBuildTest.CreateAarRunsOnceWithGeneratePackageOnBuildasserting_CreateAarruns exactly once whenGeneratePackageOnBuild=true.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets | Drops _CreateAar from the resource-update dependency chain to eliminate redundant invocations and the race window. |
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs | New regression test verifying _CreateAar executes exactly once for a library with GeneratePackageOnBuild=true. |
The `CreateAar` task writes each item in `@(JarFiles)` into the output `.aar` archive at `libs/<hash>.jar`. Today nothing protects against the same archive path being written twice: e.g. `_CompileBindingJava` adds the binding classes zip to both `@(EmbeddedJar)` and `@(AndroidJavaLibrary)`, and the input groups can otherwise accumulate duplicates across target invocations. Calling `aar.AddStream` repeatedly with the same path produces multiple zip entries with identical names, which the `DotNetPack` test now catches as `found 3` jars where only 2 unique paths exist. Track archive paths in a `HashSet<string>` and skip duplicates. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This reverts commit 3c9b3fb.
…n _CreateAar After removing _CreateAar from _UpdateAndroidResourcesDependsOn (to fix the parallel-build race in #11514), the DotNetPack test started failing because the .aar inside the .nupkg contained an extra .jar. Previously, dotnet pack''s inner _GetFrameworkAssemblyReferences evaluation implicitly re-ran _CreateAar through the resource chain (see #10270). In that inner evaluation _CompileBindingJava had not run, so the binding classes zip was not in @(EmbeddedJar)/@(AndroidJavaLibrary), and the resulting .aar (which overwrote the outer Build evaluation''s .aar) did not contain it. The .nupkg picked up that smaller .aar. Removing _CreateAar from the resource chain eliminated that overwrite, so the outer Build evaluation''s .aar (with the binding zip included in both EmbeddedJar and AndroidJavaLibrary) ended up in the .nupkg. Make _IncludeAarInNuGetPackage explicitly depend on _CreateAar so the inner per-TFM Pack evaluation still produces the .aar layout intended for the .nupkg, without relying on the resource-chain side effect that caused the original race. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
FYI I tried a workaround that does the same thing in the meantime (by adjusting |
Ok, great. The test isn't working, but when that's fixed we can get this merged, thanks! |
CI showed CreateAarRunsOnceWithGeneratePackageOnBuild failing with count=2. The binlog shows _CreateAar enters two project evaluations: * `Build` evaluation (project 71): runs completely, 39ms. * `_GetTfmSpecificContentForPackage` evaluation (project 91): entered, then immediately skipped with "Skipping target _CreateAar because it has no inputs. ... only references empty properties and/or empty item lists." The race in #11514 is on the actual file write, so what the test really needs to assert is "only one execution wrote the .aar." Counting "Target X in file" lines is too strict because MSBuild logs that line on entry, even for entries that are skipped due to empty Inputs. Switch to "Building target X completely", which is only emitted when MSBuild has decided the target will actually run. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
#11527) Fixes: #11514 Under parallel build (`-m`), library projects with `<GeneratePackageOnBuild>true</GeneratePackageOnBuild>` can intermittently fail with: ``` error XARLP7024: System.IO.IOException: The process cannot access the file '.../<assembly>.aar' because it is being used by another process. at Microsoft.Android.Build.Tasks.Files.HashFile(...) at Xamarin.Android.Tasks.ResolveLibraryProjectImports.Extract(...) ``` The reporter's `mini.binlog` shows `_CreateAar` running in two distinct project instances of the same library csproj, and a local repro shows it running **three** times for a single `dotnet build`: * Once from `BuildDependsOn`. * Once from `_UpdateAndroidResourcesDependsOn` on the regular `Build` chain. * Once more from `_UpdateAndroidResourcesDependsOn` on a Pack-dispatched project instance entered via `_GetFrameworkAssemblyReferences` (NuGet pack's per-TFM inner-target dispatch). That instance has different global properties, so MSBuild does not dedupe it and may run it on a fresh worker node, opening a write/write or write/read race against consumers that read the `.aar` via `Files.HashFile`. `_UpdateAndroidResources` is the aapt2/resource-designer step; producing the publish artifact (`.aar`) is unrelated. Remove `_CreateAar` from `_UpdateAndroidResourcesDependsOn`. The AAR is still produced because `_CreateAar` remains in `BuildDependsOn` for non-application projects, and Pack depends on `Build`, so `dotnet build` and `dotnet pack` both still create it. This collapses the count to a single invocation per build, eliminating the race window and the redundant AAR rewrites at the root rather than papering over them with reader retries or atomic writes. Added `IncrementalBuildTest.CreateAarRunsOnceWithGeneratePackageOnBuild`: builds a library with `GeneratePackageOnBuild=true` at `LoggerVerbosity.Detailed` and asserts `Target "_CreateAar" in file ...` appears exactly once in the build log. Verified against the issue's repro project: count drops from **3 to 1** with the fix.
#11527) (#11570) Backport of #11527 to `release/10.0.1xx`. Fixes: #11514 ### Why Under parallel build (`-m`), library projects with `<GeneratePackageOnBuild>true</GeneratePackageOnBuild>` can intermittently fail with: ``` error XARLP7024: System.IO.IOException: The process cannot access the file '.../<assembly>.aar' because it is being used by another process. at Microsoft.Android.Build.Tasks.Files.HashFile(...) at Xamarin.Android.Tasks.ResolveLibraryProjectImports.Extract(...) ``` `_CreateAar` was running multiple times for a single `dotnet build`: * Once from `BuildDependsOn`. * Once from `_UpdateAndroidResourcesDependsOn` on the regular `Build` chain. * Once more from `_UpdateAndroidResourcesDependsOn` on a Pack-dispatched project instance entered via `_GetFrameworkAssemblyReferences` (NuGet pack's per-TFM inner-target dispatch). That instance has different global properties, so MSBuild does not dedupe it and may run it on a fresh worker node, opening a write/write or write/read race against consumers that read the `.aar` via `Files.HashFile`. ### Approach `_UpdateAndroidResources` is the aapt2/resource-designer step; producing the publish artifact (`.aar`) is unrelated. Remove `_CreateAar` from `_UpdateAndroidResourcesDependsOn`. The AAR is still produced because `_CreateAar` remains in `BuildDependsOn` for non-application projects, and Pack depends on `Build`, so `dotnet build` and `dotnet pack` both still create it. ### Conflicts Minor merge conflict in `IncrementalBuildTest.cs` because `ManifestMergerIncremental` on `release/10.0.1xx` doesn't take the `[Values] AndroidRuntime runtime` parameter that exists on `main`. Resolved by adding the new test before the existing `ManifestMergerIncremental ()` signature.
Fixes: #11514
Why
Under parallel build (
-m), library projects with<GeneratePackageOnBuild>true</GeneratePackageOnBuild>can intermittently fail with:The reporter's
mini.binlogshows_CreateAarrunning in two distinct project instances of the same library csproj, and a local repro shows it running three times for a singledotnet build:BuildDependsOn._UpdateAndroidResourcesDependsOnon the regularBuildchain._UpdateAndroidResourcesDependsOnon a Pack-dispatched project instance entered via_GetFrameworkAssemblyReferences(NuGet pack's per-TFM inner-target dispatch). That instance has different global properties, so MSBuild does not dedupe it and may run it on a fresh worker node, opening a write/write or write/read race against consumers that read the.aarviaFiles.HashFile.Approach
_UpdateAndroidResourcesis the aapt2/resource-designer step; producing the publish artifact (.aar) is unrelated. Remove_CreateAarfrom_UpdateAndroidResourcesDependsOn. The AAR is still produced because_CreateAarremains inBuildDependsOnfor non-application projects, and Pack depends onBuild, sodotnet buildanddotnet packboth still create it.This collapses the count to a single invocation per build, eliminating the race window and the redundant AAR rewrites at the root rather than papering over them with reader retries or atomic writes.
Test
Added
IncrementalBuildTest.CreateAarRunsOnceWithGeneratePackageOnBuild: builds a library withGeneratePackageOnBuild=trueatLoggerVerbosity.Detailedand assertsTarget "_CreateAar" in file ...appears exactly once in the build log.Verified against the issue's repro project: count drops from 3 to 1 with the fix.