Avoid races publishing immutable blobs - #17444
Conversation
Use conditional blob creation when overwrites are disabled. If another publisher creates the blob concurrently, accept it only when its contents match the local asset. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c08e3391-52d8-42e3-ad8c-ef2e5be92291
There was a problem hiding this comment.
Pull request overview
This PR updates AzureStorageAssetPublisher to make publishing “immutable” Azure blobs race-safe by using conditional creates (If-None-Match: *) and, when a concurrent publisher wins, validating that the existing blob’s content matches the local file (when configured to allow identical blobs).
Changes:
- Add
BlobRequestConditions { IfNoneMatch = ETag.All }for non-overwrite uploads to make blob creation atomic. - Treat specific “already exists / precondition failed / immutable” upload failures as a concurrent-create race and fall back to existing-blob handling.
- Add unit tests covering accepted/rejected concurrent-create scenarios based on content identity.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureStorageAssetPublisher.cs | Adds conditional upload + race handling for immutable blob publishing. |
| src/Microsoft.DotNet.Build.Tasks.Feed.Tests/AzureStorageAssetPublisherTests.cs | Adds tests validating identical-vs-different concurrent-create behavior. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9236931a-f5da-4ff3-83a9-539cf86b6df0
|
@mmitche please take a look! |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a6ad3a92-2023-4c67-8948-f6d34de1a67c
There was a problem hiding this comment.
🔵 Needs a closer look
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
src/Microsoft.DotNet.Build.Tasks.Feed.Tests/AzureStorageAssetPublisherTests.cs:34
- This test computes the blob ContentHash using MD5.HashData, which can trigger weak-crypto/static-analysis findings (the production code already centralizes MD5 usage in AzureStorageExtensions.CalculateMD5 with an explicit suppression comment). Consider deriving the byte[] from AzureStorageExtensions.CalculateMD5 instead to avoid introducing a new direct MD5 usage in tests.
{
await File.WriteAllTextAsync(file, "asset contents");
byte[] contentHash = MD5.HashData(await File.ReadAllBytesAsync(file));
var blobClient = new Mock<BlobClient>();
- Files reviewed: 2/2 changed files
- Comments generated: 0 new
- Review effort level: Lite
|
@missymessa I think this might be a non-ideal fix. IIRC, the publisher is supposed to deduplicate the mappings so that if you publish to N channels, and both channels push assets to dotnetbuilds/public, then it would only try and push the asset once. In other words, the ideal model is that the system finds the mapping of asset->output location based on in channels and their config info, then dedupes that so we only try to upload each unique combo once. I would investigate and determine:
|
@JoeRobich FYI |
Summary
If-None-Match: *Motivation
Roslyn Maestro promotions 3059731 and 3059734 published to
.NET Core Tooling Devand.NET 11.0.1xx SDKsimultaneously. Both channels map blobs todotnetbuilds/public, causing all 145 unique blob paths to be scheduled twice. The currentExistsAsync()followed byUploadAsync()is a time-of-check/time-of-use race: both publishers can observe a missing blob, then the losing upload receivesBlobImmutableDueToLegalHoldafter the winning upload creates the legally held blob.This also addresses the failure pattern previously tracked by #13176 without requiring repositories to avoid otherwise-valid channel combinations.
Testing
AzureStorageAssetPublisherTests: passes forBlobAlreadyExists,BlobImmutableDueToLegalHold, andConditionNotMetraces, and rejects different existing contentMicrosoft.DotNet.XUnitV3Extensions.AlwaysFalseConditionalAssembly.Tests: missing generatedapphost.exe