Page Blob Writes in Windows Azure Storage Client Library does not support Streams with non-zero Position
Update 3/09/011: The bug is fixed in the Windows Azure SDK March 2011 release .
The current Windows Azure Storage Client Library does not support passing in a stream to CloudPageBlob.[Begin]WritePages where the stream position is a non-zero value. In such a scenario the Storage Client Library will incorrectly calculate the size of the data range which will cause the server to return HTTP 500: Internal Server Error. This is surfaced to the client via a StorageServerException with the message "Server encountered an internal error. Please try again after some time.”
HTTP 500 errors are generally retryable by the client as they relate to an issue on the server side, however in this instance it is the client which is supplying the invalid request. As such this request will be retried by the storage client library N times according to the RetryPolicy specified on the CloudBlobClient (default is 3 retries with an exponential backoff delay). However these requests will not succeed and all subsequent retries will fail with the same error.
Workarounds
In the code below I have included a set of extension methods that provide a safe way to invoke [Begin]WritePages which will throw an exception if the source stream is at a non-zero position. You can alter these methods for your specific scenario in case you wish to possibly rewind the stream. Future releases of Storage Client Library will accommodate for this scenario as we continue to expand support for PageBlob at the convenience Layer.
public static class StorageExtensions
{
/// <summary>
/// Begins an asynchronous operation to write pages to a page blob while enforcing that the stream is at the beginning
/// </summary>
/// <param name="pageData">A stream providing the page data.</param>
/// <param name="startOffset">The offset at which to begin writing, in bytes. The offset must be a multiple of 512.</param>
/// <param name="callback">The callback delegate that will receive notification when the asynchronous operation completes.</param>
/// <param name="state">A user-defined object that will be passed to the callback delegate.</param>
/// <returns>An <see cref="IAsyncResult"/> that references the asynchronous operation.</returns>
public static IAsyncResult BeginWritePagesSafe(this CloudPageBlob blobRef, Stream pageData, long startOffset, AsyncCallback callback, object state)
{1
if (pageData.Position != 0)
{
throw new InvalidOperationException("Stream position must be set to zero!");
}
return blobRef.BeginWritePages(pageData, startOffset, callback, state);
}
/// <summary>
/// Writes pages to a page blob while enforcing that the stream is at the beginning
/// </summary>
/// <param name="pageData">A stream providing the page data.</param>
/// <param name="startOffset">The offset at which to begin writing, in bytes. The offset must be a multiple of 512.</param>
public static void WritePagesSafe(this CloudPageBlob blobRef, Stream pageData, long startOffset)
{
if (pageData.Position != 0)
{
throw new InvalidOperationException("Stream position must be set to zero!");
}
blobRef.WritePages(pageData, startOffset, null);
}
/// <summary>
/// Writes pages to a page blob while enforcing that the stream is at the beginning
/// </summary>
/// <param name="pageData">A stream providing the page data.</param>
/// <param name="startOffset">The offset at which to begin writing, in bytes. The offset must be a multiple of 512.</param>
/// <param name="options">An object that specifies any additional options for the request.</param>
public static void WritePagesSafe(this CloudPageBlob blobRef, Stream pageData, long startOffset, BlobRequestOptions options)
{
if (pageData.Position != 0)
{
throw new InvalidOperationException("Stream position must be set to zero!");
}
blobRef.WritePages(pageData, startOffset, options);
}
}
Summary
The current Storage Client Library requires an additional check prior to passing in a Stream to CloudPageBlob.[Begin]WritePages in order to avoid producing an invalid request. Using the code above or applying similar checks at the application level can avoid this issue. Please note that other types of blobs are unaffected by this issue (i.e. CloudBlob.UploadFromStream) and we will be addressing this issue in a future release of the Storage Client Library.
Joe Giardino