[BUG] Concurrent uploading files may cause System.Net.Http.Headers.HttpHeaders.ReadStoreValues IndexOutOfRange exception
See original GitHub issueLibrary name and version
Azure.Storage.Blobs 12.10.0
Describe the bug
I’m having a random issue with uploading many files in parallel. It seems to be totally random, and from the related issues I find its a lock/thread safety issue. See https://github.com/dotnet/runtime/issues/61798 , where I have posted a duplicate of this report.
System.IndexOutOfRangeException: Index was outside the bounds of the array.
at System.Net.Http.Headers.HttpHeaders.ReadStoreValues[T](Span`1 values, Object storeValue, HttpHeaderParser parser, Int32& currentIndex)
-- SNIP (See below for full trace)
It happens in a dotnet 6 console app running from dotnet run
on a TeamCity Agent. It seems very random and triggers in about 1/10 sessions when I upload around 1000 tiny files to the blob container. I do NOT have New Relic Monitoring
installed, as has been an identified issue in the other similar report ( #25501 ) .
I’m using Azure.Storage.Blobs v12.10.0
(Latest at the time of writing), and no other NuGet packages.
Source code (slightly modified): (Inspired by this official example: https://docs.microsoft.com/en-us/azure/storage/blobs/storage-blob-scalable-app-upload-files?tabs=dotnet )
var blobContainerClient = new BlobContainerClient(new Uri(assetUploadToken.UploadDestination));
BlobUploadOptions uploadOptions = new BlobUploadOptions()
{
TransferOptions = new StorageTransferOptions()
{
MaximumConcurrency = 8
}
};
var tasks = new Queue<Task<Response<BlobContentInfo>>>();
foreach (string sourceFilePath in Directory.GetFiles(fileDirectoryPath, "*", searchOption)
.Where(x => regexFileExtensions.Match(x).Success))
{
var fileToUpload = new FileInfo(sourceFilePath);
var blobTargetPath = $"my-files";
var blobClient = blobContainerClient.GetBlobClient(blobTargetPath);
var uploadTask = blobClient.UploadAsync(fileToUpload.FullName, uploadOptions);
tasks.Enqueue(uploadTask);
}
// Run all the tasks asynchronously. The `uploadOptions` will handle concurrency.
await Task.WhenAll(tasks);
Console.WriteLine($"Uploaded {tasks.Count} files.");
Stack Trace:
System.IndexOutOfRangeException: Index was outside the bounds of the array.
at System.Net.Http.Headers.HttpHeaders.ReadStoreValues[T](Span`1 values, Object storeValue, HttpHeaderParser parser, Int32& currentIndex)
-- SNIP (See below for full trace)
Expected behavior
Uploading succeeds without issue.
Actual behavior
Uploading randomly fails (maybe in 1/10 uploading sessions) with the stack trace given in the description.
Full stack trace
System.IndexOutOfRangeException: Index was outside the bounds of the array.
at System.Net.Http.Headers.HttpHeaders.ReadStoreValues[T](Span`1 values, Object storeValue, HttpHeaderParser parser, Int32& currentIndex)
at System.Net.Http.Headers.HttpHeaders.GetStoreValuesAsStringOrStringArray(HeaderDescriptor descriptor, Object sourceValues, String& singleValue, String[]& multiValue)
at System.Net.Http.Headers.HttpHeaders.GetStoreValuesAsStringArray(HeaderDescriptor descriptor, HeaderStoreItemInfo info)
at System.Net.Http.Headers.HttpHeaders.TryGetValues(HeaderDescriptor descriptor, IEnumerable`1& values)
at System.Net.Http.HttpConnectionPool.EstablishProxyTunnelAsync(Boolean async, HttpRequestHeaders headers, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.AddHttp11ConnectionAsync(HttpRequestMessage request)
at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.WaitWithCancellationAsync(CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.GetHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
at Azure.Core.Pipeline.HttpClientTransport.ProcessAsync(HttpMessage message, Boolean async)
at Azure.Core.Pipeline.ResponseBodyPolicy.ProcessAsync(HttpMessage message, ReadOnlyMemory`1 pipeline, Boolean async)
at Azure.Core.Pipeline.HttpPipelineSynchronousPolicy.<ProcessAsync>g__ProcessAsyncInner|4_0(HttpMessage message, ReadOnlyMemory`1 pipeline)
at Azure.Core.Pipeline.RedirectPolicy.ProcessAsync(HttpMessage message, ReadOnlyMemory`1 pipeline, Boolean async)
at Azure.Core.Pipeline.RetryPolicy.ProcessAsync(HttpMessage message, ReadOnlyMemory`1 pipeline, Boolean async)
at Azure.Core.Pipeline.RetryPolicy.ProcessAsync(HttpMessage message, ReadOnlyMemory`1 pipeline, Boolean async)
at Azure.Storage.Blobs.BlockBlobRestClient.UploadAsync(Int64 contentLength, Stream body, Nullable`1 timeout, Byte[] transactionalContentMD5, String blobContentType, String blobContentEncoding, String blobContentLanguage, Byte[] blobContentMD5, String blobCacheControl, IDictionary`2 metadata, String leaseId, String blobContentDisposition, String encryptionKey, String encryptionKeySha256, Nullable`1 encryptionAlgorithm, String encryptionScope, Nullable`1 tier, Nullable`1 ifModifiedSince, Nullable`1 ifUnmodifiedSince, String ifMatch, String ifNoneMatch, String ifTags, String blobTagsString, Nullable`1 immutabilityPolicyExpiry, Nullable`1 immutabilityPolicyMode, Nullable`1 legalHold, CancellationToken cancellationToken)
at Azure.Storage.Blobs.Specialized.BlockBlobClient.UploadInternal(Stream content, BlobHttpHeaders blobHttpHeaders, IDictionary`2 metadata, IDictionary`2 tags, BlobRequestConditions conditions, Nullable`1 accessTier, BlobImmutabilityPolicy immutabilityPolicy, Nullable`1 legalHold, IProgress`1 progressHandler, String operationName, Boolean async, CancellationToken cancellationToken)
at Azure.Storage.Blobs.Specialized.BlockBlobClient.<>c__DisplayClass62_0.<<GetPartitionedUploaderBehaviors>b__0>d.MoveNext()
--- End of stack trace from previous location ---
at Azure.Storage.PartitionedUploader`2.UploadInternal(Stream content, TServiceSpecificArgs args, IProgress`1 progressHandler, Boolean async, CancellationToken cancellationToken)
at Azure.Storage.Blobs.BlobClient.StagedUploadInternal(Stream content, BlobUploadOptions options, Boolean async, CancellationToken cancellationToken)
at Azure.Storage.Blobs.BlobClient.UploadAsync(String path, BlobUploadOptions options, CancellationToken cancellationToken)
at UploadArtifacts.UploadArtifacts.ModelDistributionApiFetcher.UploadFiles(AssetUploadToken assetUploadToken, String fileDirectoryPath, String fileExtensionRegex, Boolean searchAllSubDirectories)
Reproduction Steps
See the code in the description. I upload around 1000 tiny files using this script, and it happens just rarely enough to having troubles recreating it.
Environment
dotnet --info:
Hosting information: .NET SDK (reflecting any global.json): Version: 6.0.101 Commit: ef49f6213a
Runtime Environment: OS Name: Windows OS Version: 10.0.14393 OS Platform: Windows RID: win10-x64 Base Path: C:\Program Files\dotnet\sdk\6.0.101\
Host (useful for support): Version: 6.0.1 Commit: 3a25a7f1cc
Other info
Windows Server 2016 TeamCity 2021.2.2
Issue Analytics
- State:
- Created 2 years ago
- Reactions:1
- Comments:8 (3 by maintainers)
Top GitHub Comments
I believe we’re hitting the same issue, but with downloads instead of uploads. Is
BlobContainerClient
not intended safe for parallel operations?Our error looks like this:
Sorry for the late response.
It’s probably been acknowledged that the fix was made already on the dotnet side for the concurrency issue regarding HttpHeaders however the fix is in .NET 7.0 and it has been communicated that it will not be backported to .NET 6.0 https://github.com/dotnet/runtime/pull/68115#issuecomment-1121589331
Another mitigation that can be taken (aside from upgrading to .NET 7.0) is to provide a custom HttpMessageHandler that will prevent the race condition, which can be provided through the
ClientOptions
.