Skip to content

Commit

Permalink
Fixed bug in Open Read, grouped optional parameters (Azure#14511)
Browse files Browse the repository at this point in the history
  • Loading branch information
seanmcc-msft authored Aug 27, 2020
1 parent 6655f73 commit 8bcc441
Show file tree
Hide file tree
Showing 35 changed files with 2,724 additions and 8,476 deletions.
2 changes: 1 addition & 1 deletion sdk/storage/Azure.Storage.Blobs.Batch/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## 12.4.0-preview.1 (Unreleased)

## 12.3.1 (2020-08-18)
- Bug in TaskExtensions.EnsureCompleted method that causes it to unconditionally throw an exception in the environments with synchronization context
- Fixed bug in TaskExtensions.EnsureCompleted method that causes it to unconditionally throw an exception in the environments with synchronization context

## 12.3.0 (2020-08-13)
- Includes all features from 12.3.0-preview.1 through 12.3.0-preview.2.
Expand Down
2 changes: 1 addition & 1 deletion sdk/storage/Azure.Storage.Blobs.ChangeFeed/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## 12.0.0-preview.5 (Unreleased)

## 12.0.0-preview.4 (2020-08-18)
- Bug in TaskExtensions.EnsureCompleted method that causes it to unconditionally throw an exception in the environments with synchronization context
- Fixed bug in TaskExtensions.EnsureCompleted method that causes it to unconditionally throw an exception in the environments with synchronization context

## 12.0.0-preview.3 (2020-08-13)
- This release contains bug fixes to improve quality.
Expand Down
3 changes: 2 additions & 1 deletion sdk/storage/Azure.Storage.Blobs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Release History

## 12.6.0-preview.1 (Unreleased)
- Fixed bug in BlobBaseClient.OpenRead() causing us to do more download called than necessary.

## 12.5.1 (2020-08-18)
- Bug in TaskExtensions.EnsureCompleted method that causes it to unconditionally throw an exception in the environments with synchronization context
- Fixed bug in TaskExtensions.EnsureCompleted method that causes it to unconditionally throw an exception in the environments with synchronization context

## 12.5.0 (2020-08-13)
- Includes all features from 12.5.0-preview.1 through 12.5.0-preview.6.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,13 @@ public BlobMetrics() { }
public Azure.Storage.Blobs.Models.BlobRetentionPolicy RetentionPolicy { get { throw null; } set { } }
public string Version { get { throw null; } set { } }
}
public partial class BlobOpenReadOptions
{
public BlobOpenReadOptions(bool allowModifications) { }
public int? BufferSize { get { throw null; } set { } }
public Azure.Storage.Blobs.Models.BlobRequestConditions Conditions { get { throw null; } set { } }
public long Position { get { throw null; } set { } }
}
public partial class BlobProperties
{
public BlobProperties() { }
Expand Down Expand Up @@ -1219,9 +1226,15 @@ public BlobBaseClient(System.Uri blobUri, Azure.Storage.StorageSharedKeyCredenti
public virtual System.Threading.Tasks.Task<Azure.Response<Azure.Storage.Blobs.Models.BlobProperties>> GetPropertiesAsync(Azure.Storage.Blobs.Models.BlobRequestConditions conditions = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual Azure.Response<Azure.Storage.Blobs.Models.GetBlobTagResult> GetTags(Azure.Storage.Blobs.Models.BlobRequestConditions conditions = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Threading.Tasks.Task<Azure.Response<Azure.Storage.Blobs.Models.GetBlobTagResult>> GetTagsAsync(Azure.Storage.Blobs.Models.BlobRequestConditions conditions = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.IO.Stream OpenRead(Azure.Storage.Blobs.Models.BlobOpenReadOptions options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public virtual System.IO.Stream OpenRead(bool allowBlobModifications, long position = (long)0, int? bufferSize = default(int?), System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public virtual System.IO.Stream OpenRead(long position = (long)0, int? bufferSize = default(int?), Azure.Storage.Blobs.Models.BlobRequestConditions conditions = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Threading.Tasks.Task<System.IO.Stream> OpenReadAsync(Azure.Storage.Blobs.Models.BlobOpenReadOptions options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public virtual System.Threading.Tasks.Task<System.IO.Stream> OpenReadAsync(bool allowBlobModifications, long position = (long)0, int? bufferSize = default(int?), System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public virtual System.Threading.Tasks.Task<System.IO.Stream> OpenReadAsync(long position = (long)0, int? bufferSize = default(int?), Azure.Storage.Blobs.Models.BlobRequestConditions conditions = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual Azure.Response SetAccessTier(Azure.Storage.Blobs.Models.AccessTier accessTier, Azure.Storage.Blobs.Models.BlobRequestConditions conditions = null, Azure.Storage.Blobs.Models.RehydratePriority? rehydratePriority = default(Azure.Storage.Blobs.Models.RehydratePriority?), System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Threading.Tasks.Task<Azure.Response> SetAccessTierAsync(Azure.Storage.Blobs.Models.AccessTier accessTier, Azure.Storage.Blobs.Models.BlobRequestConditions conditions = null, Azure.Storage.Blobs.Models.RehydratePriority? rehydratePriority = default(Azure.Storage.Blobs.Models.RehydratePriority?), System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
Expand Down
62 changes: 60 additions & 2 deletions sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1324,6 +1324,60 @@ internal async Task<Response> StagedDownloadAsync(
#endregion Parallel Download

#region OpenRead
/// <summary>
/// Opens a stream for reading from the blob. The stream will only download
/// the blob as the stream is read from.
/// </summary>
/// <param name="options">
/// Optional parameters.
/// </param>
/// <param name="cancellationToken">
/// Optional <see cref="CancellationToken"/> to propagate
/// notifications that the operation should be cancelled.
/// </param>
/// <returns>
/// Returns a stream that will download the blob as the stream
/// is read from.
/// </returns>
#pragma warning disable AZC0015 // Unexpected client method return type.
public virtual Stream OpenRead(
#pragma warning restore AZC0015 // Unexpected client method return type.
BlobOpenReadOptions options,
CancellationToken cancellationToken = default)
=> OpenReadInternal(
options?.Position ?? 0,
options?.BufferSize,
options?.Conditions,
async: false,
cancellationToken).EnsureCompleted();

/// <summary>
/// Opens a stream for reading from the blob. The stream will only download
/// the blob as the stream is read from.
/// </summary>
/// <param name="options">
/// Optional parameters.
/// </param>
/// <param name="cancellationToken">
/// Optional <see cref="CancellationToken"/> to propagate
/// notifications that the operation should be cancelled.
/// </param>
/// <returns>
/// Returns a stream that will download the blob as the stream
/// is read from.
/// </returns>
#pragma warning disable AZC0015 // Unexpected client method return type.
public virtual async Task<Stream> OpenReadAsync(
#pragma warning restore AZC0015 // Unexpected client method return type.
BlobOpenReadOptions options,
CancellationToken cancellationToken = default)
=> await OpenReadInternal(
options.Position,
options?.BufferSize,
options?.Conditions,
async: true,
cancellationToken).ConfigureAwait(false);

/// <summary>
/// Opens a stream for reading from the blob. The stream will only download
/// the blob as the stream is read from.
Expand All @@ -1348,10 +1402,11 @@ internal async Task<Response> StagedDownloadAsync(
/// Returns a stream that will download the blob as the stream
/// is read from.
/// </returns>
[EditorBrowsable(EditorBrowsableState.Never)]
#pragma warning disable AZC0015 // Unexpected client method return type.
public virtual Stream OpenRead(
#pragma warning restore AZC0015 // Unexpected client method return type.
long position = 0,
long position = 0 ,
int? bufferSize = default,
BlobRequestConditions conditions = default,
CancellationToken cancellationToken = default)
Expand Down Expand Up @@ -1385,6 +1440,7 @@ public virtual Stream OpenRead(
/// Returns a stream that will download the blob as the stream
/// is read from.
/// </returns>
[EditorBrowsable(EditorBrowsableState.Never)]
#pragma warning disable AZC0015 // Unexpected client method return type.
public virtual Stream OpenRead(
#pragma warning restore AZC0015 // Unexpected client method return type.
Expand Down Expand Up @@ -1422,6 +1478,7 @@ public virtual Stream OpenRead(
/// Returns a stream that will download the blob as the stream
/// is read from.
/// </returns>
[EditorBrowsable(EditorBrowsableState.Never)]
#pragma warning disable AZC0015 // Unexpected client method return type.
public virtual async Task<Stream> OpenReadAsync(
#pragma warning restore AZC0015 // Unexpected client method return type.
Expand Down Expand Up @@ -1459,11 +1516,12 @@ public virtual async Task<Stream> OpenReadAsync(
/// Returns a stream that will download the blob as the stream
/// is read from.
/// </returns>
[EditorBrowsable(EditorBrowsableState.Never)]
#pragma warning disable AZC0015 // Unexpected client method return type.
public virtual async Task<Stream> OpenReadAsync(
#pragma warning restore AZC0015 // Unexpected client method return type.
bool allowBlobModifications,
long position = 0,
long position = 0 ,
int? bufferSize = default,
CancellationToken cancellationToken = default)
=> await OpenReadAsync(
Expand Down
49 changes: 49 additions & 0 deletions sdk/storage/Azure.Storage.Blobs/src/Models/BlobOpenReadOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Azure.Storage.Blobs.Specialized;

namespace Azure.Storage.Blobs.Models
{
/// <summary>
/// Optional parameters for <see cref="BlobBaseClient.OpenReadAsync(BlobOpenReadOptions, System.Threading.CancellationToken)"/>
/// </summary>
public class BlobOpenReadOptions
{
/// <summary>
/// The position within the blob to begin the stream.
/// Defaults to the beginning of the blob.
/// </summary>
public long Position { get; set; }

/// <summary>
/// The buffer size to use when the stream downloads parts
/// of the blob. Defaults to 4 MB.
/// </summary>
public int? BufferSize { get; set; }

/// <summary>
/// Optional <see cref="BlobRequestConditions"/> to add conditions on
/// the download of the blob.
/// </summary>
public BlobRequestConditions Conditions { get; set; }

/// <summary>
/// Constructor.
/// </summary>
/// <param name="allowModifications">
/// If false, a <see cref="RequestFailedException"/> will be thrown if the blob is modified while
/// it is being read from.
/// </param>
public BlobOpenReadOptions(bool allowModifications)
{
// Setting the Conditions to empty means we won't automatically
// use the ETag as a condition and it will be possible for the blob
// to change while it's being read from.
if (allowModifications)
{
Conditions = new BlobRequestConditions();
}
}
}
}
78 changes: 63 additions & 15 deletions sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -957,8 +957,13 @@ public async Task OpenReadAsync_BufferSize()
using Stream stream = new MemoryStream(data);
await blob.UploadAsync(stream);

BlobOpenReadOptions options = new BlobOpenReadOptions(allowModifications: false)
{
BufferSize = size / 8
};

// Act
Stream outputStream = await blob.OpenReadAsync(bufferSize: size / 8).ConfigureAwait(false);
Stream outputStream = await blob.OpenReadAsync(options).ConfigureAwait(false);
byte[] outputBytes = new byte[size];
int downloadedBytes = 0;

Expand Down Expand Up @@ -987,11 +992,14 @@ public async Task OpenReadAsync_OffsetAndBufferSize()
byte[] expected = new byte[size];
Array.Copy(data, size / 2, expected, size / 2, size / 2);

BlobOpenReadOptions options = new BlobOpenReadOptions(allowModifications: false)
{
Position = size / 2,
BufferSize = size / 8
};

// Act
Stream outputStream = await blob.OpenReadAsync(
position: size / 2,
bufferSize: size / 8)
.ConfigureAwait(false);
Stream outputStream = await blob.OpenReadAsync(options).ConfigureAwait(false);
byte[] outputBytes = new byte[size];

int downloadedBytes = size / 2;
Expand Down Expand Up @@ -1041,10 +1049,14 @@ public async Task OpenReadAsync_AccessConditions()
parameters: parameters,
lease: true);

BlobOpenReadOptions options = new BlobOpenReadOptions(allowModifications: false)
{
Conditions = accessConditions,
BufferSize = size / 4
};

// Act
Stream outputStream = await blob.OpenReadAsync(
bufferSize: size / 4,
conditions: accessConditions).ConfigureAwait(false);
Stream outputStream = await blob.OpenReadAsync(options).ConfigureAwait(false);
byte[] outputBytes = new byte[size];

int downloadedBytes = 0;
Expand Down Expand Up @@ -1077,10 +1089,14 @@ public async Task OpenReadAsync_AccessConditionsFail()
parameters.NoneMatch = await SetupBlobMatchCondition(blob, parameters.NoneMatch);
BlobRequestConditions accessConditions = BuildAccessConditions(parameters);

BlobOpenReadOptions options = new BlobOpenReadOptions(allowModifications: false)
{
Conditions = accessConditions,
BufferSize = size / 4
};

// Act
Stream outputStream = await blob.OpenReadAsync(
bufferSize: size / 4,
conditions: accessConditions).ConfigureAwait(false);
Stream outputStream = await blob.OpenReadAsync(options).ConfigureAwait(false);
byte[] outputBytes = new byte[size];

await TestHelper.CatchAsync<Exception>(
Expand All @@ -1103,7 +1119,13 @@ public async Task OpenReadAsync_StrangeOffsetsTest()
using Stream stream = new MemoryStream(exectedData);
await blobClient.UploadAsync(stream);

Stream outputStream = await blobClient.OpenReadAsync(position: 0, bufferSize: 157);
BlobOpenReadOptions options = new BlobOpenReadOptions(allowModifications: false)
{
Position = 0,
BufferSize = 157
};

Stream outputStream = await blobClient.OpenReadAsync(options);
byte[] actualData = new byte[size];
int offset = 0;

Expand Down Expand Up @@ -1139,8 +1161,13 @@ public async Task OpenReadAsync_Modified()
using Stream stream = new MemoryStream(data);
await blob.UploadAsync(stream);

BlobOpenReadOptions options = new BlobOpenReadOptions(allowModifications: false)
{
BufferSize = size / 2
};

// Act
Stream outputStream = await blob.OpenReadAsync().ConfigureAwait(false);
Stream outputStream = await blob.OpenReadAsync(options).ConfigureAwait(false);
byte[] outputBytes = new byte[size];
await outputStream.ReadAsync(outputBytes, 0, size / 2);

Expand Down Expand Up @@ -1184,9 +1211,10 @@ await blob.StageBlockAsync(

await blob.CommitBlockListAsync(new List<string> { blockId0 });

BlobOpenReadOptions options = new BlobOpenReadOptions(allowModifications: true);

// Act
Stream outputStream = await blob.OpenReadAsync(
allowBlobModifications: true).ConfigureAwait(false);
Stream outputStream = await blob.OpenReadAsync(options).ConfigureAwait(false);
byte[] outputBytes = new byte[2 * size];
await outputStream.ReadAsync(outputBytes, 0, size);

Expand Down Expand Up @@ -1244,6 +1272,26 @@ await blobClient.UploadAsync(stream,
}
}

[Test]
public async Task OpenReadAsync_CopyReadStreamToAnotherStream()
{
// Arrange
await using DisposingContainer test = await GetTestContainerAsync();
long size = 4 * Constants.MB;
byte[] exectedData = GetRandomBuffer(size);
BlobClient blobClient = InstrumentClient(test.Container.GetBlobClient(GetNewBlobName()));
using Stream stream = new MemoryStream(exectedData);
await blobClient.UploadAsync(stream);

MemoryStream outputStream = new MemoryStream();

// Act
using Stream blobStream = await blobClient.OpenReadAsync();
await blobStream.CopyToAsync(outputStream);

TestHelper.AssertSequenceEqual(exectedData, outputStream.ToArray());
}

[Test]
public async Task OpenReadAsync_InvalidParameterTests()
{
Expand Down
Loading

0 comments on commit 8bcc441

Please sign in to comment.