Search code examples
c#azureconcurrencyblobstorage

Azure CloudAppendBlob errors with concurrent access


My understanding was that the Azure CloudAppendBlob was safe from concurrency issues as you can only append to this blob storage and it does not need to compare E-tags. As stated by this post:

http://blogs.msdn.com/b/windowsazurestorage/archive/2015/04/13/introducing-azure-storage-append-blob.aspx

specifically:

In addition, Append Blob supports having multiple clients writing to the same blob without any need for synchronization (unlike block and page blob)

However the following unit test raises:

412 the append position condition specified was not met.

stack trace

Microsoft.WindowsAzure.Storage.Blob.BlobWriteStream.Flush()
Microsoft.WindowsAzure.Storage.Blob.BlobWriteStream.Commit()
Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.UploadFromStreamHelper
Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.AppendFromStream
Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.AppendFromByteArray
Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.AppendText

Here is the unit test. Maybe the service will handle requests from different contexts but not like this as a parallel?

    [TestMethod]
    public void test_append_text_concurrency()
    {
        AppendBlobStorage abs = new AppendBlobStorage(new    TestConnectConfig(), "testappendblob");

        string filename = "test-concurrent-blob";

        abs.Delete(filename);                       

        Parallel.Invoke(
            () => { abs.AppendText(filename, "message1\r\n"); },
            () => { abs.AppendText(filename, "message2\r\n"); }
        );

        string text = abs.ReadText(filename);

        Assert.IsTrue(text.Contains("message1"));
        Assert.IsTrue(text.Contains("message2"));
    }

Method in AppendBlobStorage

    public void AppendText(string filename, string text)
    {
        CloudAppendBlob cab = m_BlobStorage.BlobContainer.GetAppendBlobReference(filename);

        // Create if it doesn't exist
        if (!cab.Exists())
        {
            try
            {
                cab.CreateOrReplace(AccessCondition.GenerateIfNotExistsCondition(), null, null);
            }
            catch { }
        }

        // Append the text
        cab.AppendText(text);      
    }

Maybe I'm missing something. The reason I'm trying to do this as I have multiple web jobs which can all write to this append blob and I figured this was what it was designed for?


Solution

  • After a bit more searching it looks like this is an actual problem.

    I guess AppendBlobStorage is fairly new. (There are also other issues at the moment with AppendBlobStorage. see

    http://blogs.msdn.com/b/windowsazurestorage/archive/2015/09/02/issue-in-azure-storage-client-library-5-0-0-and-5-0-1-preview-in-appendblob-functionality.aspx)

    Anyway I fixed the issue by using the AppendBlock varient rather than AppendText as suggested here:

    https://azurekan.wordpress.com/2015/09/08/issues-with-adding-text-to-azure-storage-append-blob/

    The change to the appendtext method which passes the unit test defined above

        public void AppendText(string filename, string text)
        {
            if (string.IsNullOrWhiteSpace(filename))
                throw new ArgumentException("filename cannot be null or empty");
    
            if (!string.IsNullOrEmpty(text))
            {
                CloudAppendBlob cab = m_BlobStorage.BlobContainer.GetAppendBlobReference(filename);
    
                // Create if it doesn't exist
                if (!cab.Exists())
                {
                    try
                    {
                        cab.CreateOrReplace(AccessCondition.GenerateIfNotExistsCondition(), null, null);
                    }
                    catch (StorageException) { }
                }
    
                // use append block as append text seems to have an error at the moment.
                using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(text)))
                {
                    cab.AppendBlock(ms);
                }
            }
    
        }