Dealing with Azure’s StorageException: Object reference not set… using mono

von Patrick Bédat, 17. April 2016

TL;DR

This article will show you
– how to utilize docker to make a bug reproducible
– how to solve the problems with the Azure Storage Client library

The bug

When our client told me we had to export some files to the Azure Storage of some of his clients I thought: Awesome! I finally get in touch with Azure. Getting in touch with cloud services is almost everytime a good chance to get some new impressions on how to tailor APIs for the web. But it turned out different this time…

So I added the WindowsAzure.Storage package from NuGet and was thrilled how easy the implementation was:

var account = new CloudStorageAccount (new StorageCredentials (accountName, accountKey), true);

var blobClient = account.CreateCloudBlobClient();

var container = blobClient.GetContainerReference(containerId);
container.CreateIfNotExists();

var blob = container.GetBlockBlobReference(Path.GetFileName(file));
blob.UploadFromFile (file);

Charming isn’t it? And it was until files got bigger. When working for Media Carrier we are often pushing gigabytes of data over the wire and that’s where the problem started:

Microsoft.WindowsAzure.Storage.StorageException: Object reference not set to an instance of an object
---> System.NullReferenceException: Object reference not set to an instance of an object
  at System.Net.WebConnectionStream.EndRead (IAsyncResult r) <0x41fc7860 + 0x0009e> in <filename unknown>:0 
  at Microsoft.WindowsAzure.Storage.Core.ByteCountingStream.EndRead (IAsyncResult asyncResult) <0x41fbc1c0 + 0x00024> in <filename unknown>:0 
  at Microsoft.WindowsAzure.Storage.Core.Util.AsyncStreamCopier`1[T].ProcessEndRead () <0x41fd7f10 + 0x0003b> in <filename unknown>:0 
  at Microsoft.WindowsAzure.Storage.Core.Util.AsyncStreamCopier`1[T].EndOperation (IAsyncResult res) <0x41fd71c0 + 0x00067> in <filename unknown>:0 
  at Microsoft.WindowsAzure.Storage.Core.Util.AsyncStreamCopier`1[T].EndOpWithCatch (IAsyncResult res) <0x41fd6e80 + 0x00073> in <filename unknown>:0 
  --- End of inner exception stack trace ---
  at Microsoft.WindowsAzure.Storage.Blob.BlobWriteStream.Flush () <0x41fdd180 + 0x0007b> in <filename unknown>:0 
  at Microsoft.WindowsAzure.Storage.Blob.BlobWriteStream.Commit () <0x41fdcf40 + 0x00023> in <filename unknown>:0 
  at Microsoft.WindowsAzure.Storage.Blob.BlobWriteStream.Dispose (Boolean disposing) <0x41fdced0 + 0x00043> in <filename unknown>:0 
  at System.IO.Stream.Close () <0x7f1e247c53d0 + 0x00019> in <filename unknown>:0 
  at System.IO.Stream.Dispose () <0x7f1e247c5400 + 0x00013> in <filename unknown>:0 
  at Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob.UploadFromStreamHelper (System.IO.Stream source, Nullable`1 length, Microsoft.WindowsAzure.Storage.AccessCondition accessCondition, Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions options, Microsoft.WindowsAzure.Storage.OperationContext operationContext) <0x41fc9e10 + 0x009e0> in <filename unknown>:0 
  at Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob.UploadFromStream (System.IO.Stream source, Microsoft.WindowsAzure.Storage.AccessCondition accessCondition, Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions options, Microsoft.WindowsAzure.Storage.OperationContext operationContext) <0x41fc9db0 + 0x0004b> in <filename unknown>:0 
  at Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob.UploadFromFile (System.String path, Microsoft.WindowsAzure.Storage.AccessCondition accessCondition, Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions options, Microsoft.WindowsAzure.Storage.OperationContext operationContext) <0x41fc9cc0 + 0x00097> in <filename unknown>:0 
  at windowsstoragebug.MainClass.Main (System.String[] args) <0x41f1cd60 + 0x004f0> in <filename unknown>:0 
Request Information
RequestID:c4dedc47-0001-0038-734d-962acc000000
RequestDate:Thu, 14 Apr 2016 12:56:26 GMT
StatusMessage:Created

And my first thought was: „What did I do wrong?“. So I began messing around with the client settings:

blobClient.DefaultRequestOptions.ServerTimeout = new TimeSpan (1, 0, 0);
blobClient.DefaultRequestOptions.MaximumExecutionTime = new TimeSpan (1, 0, 0);
blobClient.DefaultRequestOptions.SingleBlobUploadThresholdInBytes = 67108864; //64M

Nock luck. So I googled: https://github.com/Azure/azure-storage-net/issues/202. It is a similar issue, but the proposed workarounds didn’t help.
It was time to dive a bit deeper into the Azure Storage service. How does it work?

Azure Storage and PutBlock

Azure storage (similar to S3 in AWS) can be used to store files. The files are stored as blobs – block blobs in this case. Block blobs are organized in containers (would be buckets in S3).
You can either upload a BlockBlob in a single transaction – when the block blob is < 64M, or upload the blob separated in blocks, each < 4M with a maximum of 50000 blocks. Each block gets a unique block id in natural order. When all blocks have been transmitted to azure you commit the transaction by sending the whole list of block ids.

See https://msdn.microsoft.com/de-de/library/azure/ee691974.aspx

I felt lucky, when I saw, that the azure client offered a PutBlock and a PutBlockList method. So I tried to upload the file in chunks:

using(var stream = File.OpenRead(file))
{
    int position = 0;
    const int BLOCK_SIZE = 4 * 1024 * 1024;
    int currentBlockSize = BLOCK_SIZE;

    var blockIds = new List<string>();
    var blockId = 0;

    while(currentBlockSize == BLOCK_SIZE)
    {
        if ((position + currentBlockSize) > stream.Length)
            currentBlockSize = (int)stream.Length - position;

        if(currentBlockSize == 0)
            continue;

        byte[] chunk = new byte[currentBlockSize];
        stream.Read (chunk, 0, currentBlockSize);

        var base64BlockId = Convert.ToBase64String(System.Text.Encoding.Default.GetBytes(blockId.ToString("d5")));

        using(var memoryStream = new MemoryStream(chunk))
        {
            memoryStream.Position = 0;                  
            blob.PutBlock(base64BlockId, memoryStream, null);
        }

        blockIds.Add(base64BlockId);

        position += currentBlockSize;
        blockId++;

    }

    blob.PutBlockList(blockIds);
}

I ran it on my machine and started to burst into dancing. Not for long though… After deploying it, uploading suddenly stuck. Then on my maching (and later on the staging server) it suddenly gave me those lines:

_wapi_handle_ref: Attempting to ref unused handle 0x4af
_wapi_handle_unref_full: Attempting to unref unused handle 0x4af

which means that something is very very wrong…

Challenge accepted

When a 3rd party library isn’t working correctly, I’m usually trying to root out possible errors on my side. So after 2 a.m. I did some funny things with memory streams, that I really don’t want to show you. So after my PutBlock experiment didn’t work out my motivation started to multiply. I cannot rest when some 3rd party library, which is supposed to just work, simply doesn’t bend to my will.

My next plan was to implement parts of the storage client against the azure REST api.

Being a proud and arrogant developer, I believed, that I could hack that WebRequest code for azure together in minutes… Behold the Authorization header. What a pain in the ass seriously. Just take a look at this: https://msdn.microsoft.com/de-de/library/azure/dd179428.aspx

Then I came across this beatiful article.

With the help of the article above I was finally able to write my own implementation of PutBlock (I snatched CreateRESTRequest from the article above):

var request = CreateRESTRequest ("PUT", $"{container.Name}/{blobId}?comp=block&blockid={base64BlockId}", chunk);    

var response = request.GetResponse () as HttpWebResponse;

using (var responseStream = new StreamReader(response.GetResponseStream ()))
{
    var output = responseStream.ReadToEnd ();
    if (!string.IsNullOrEmpty (output))
        yield return output;
}

AND IT WORKED LIKE A CHARM. And still does.

Making the bug reproducible

The journey doesn’t end here.

The azure storage lib is open source and whenever you are using open source software you should give something back from time to time. Either through contributions, money or by being a good bug reporter.
Filing an issue on github is easy, but if you want to have it fixed, make sure it is easily reproducible. This saves a lot of time for the hard working open source contributors.

So how do we get there? The bug happened on my Ubuntu linux 14.04 running mono Stable 4.2.3.4/832de4b. Telling somebody to setup a VM, checkout my sample code, compile it and run it, would be a lot to ask.
That’s where Docker comes into play.

To run the desired environment, we have to write a Dockerfile:

# Define the base image
FROM ubuntu:14.04
MAINTAINER Patrick Bédat <patrick.bedat@ixts.de>

# Add the mono sources
RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
RUN echo "deb http://download.mono-project.com/repo/debian wheezy main" | tee /etc/apt/sources.list.d/mono-xamarin.list

# Install mono and git
RUN apt-get update && \
    apt-get install -y \
    git mono-complete

RUN mozroots --import --sync

# Clone the sample project and build it
RUN git clone git://github.com/pbedat/azure-mono-bug.git && \
    cd azure-mono-bug && \
    mono NuGet.exe restore && \
    xbuild azure-storage-bug.sln

# This tells docker what to do when we run this image
ENTRYPOINT ["mono", "/azure-mono-bug/azure-storage-bug/bin/Debug/azure-storage-bug.exe"]

From this dockerfile you’re able to build an image:

docker build -t azure-mono-bug ./azure-mono-bug

Then you can run containers based on this image

docker run azure-mono-bug <account-name> <account-key>

The application then
– creates a 500 MB file
– tries to upload it with UploadFromFile to azure
– tries again with the PutBlock method

Conclusion

I’ve pushed the image to the docker hub. Now anybody using docker, can run the sample app by typing

pbedat/azure-mono-bug <account-name> <account-key>

Nuff said.