1
votes

I've reproduced an issue in a sample repo where an unhandled exception in a WebForms project is getting caught in the Application_Error method in Global.asax. I need that error log to write to Azure Blob Storage. The Blob Storage code is working for handled errors within the application as well as when pulled into a console app. However, an unhandled exception will hang indefinitely (not fail) on the "containerClient.CreateIfNotExistsAsync()" method (which is the first http call in the chain).

I believe the Blob Storage SDK does return an http 409 [Conflict] when checking if the container exists. But when I comment that line out (and the container exists), it shows the same behavior when trying to upload the file.

Any ideas why this hangs?

Reproducible in this repo, just create your own storage account, add yourself as a blob contributor in IAM, update the Web.Config with the account and container name.

Some code snippets

    protected void Page_Load(object sender, EventArgs e) // Default.aspx
    {
        throw new Exception("is this the exception you are looking for?");
    }


    protected void Application_Error(object sender, EventArgs e) //Global.asax
    {
        Exception ex = Server.GetLastError();

        Logger.AddLog(ex, string.Empty);
        if (ex.InnerException != null) Logger.AddLog(ex.InnerException, string.Empty);
        this.Response.Redirect("Error.aspx", true);
    }

    internal static void AddLog(Exception ex, string empty) // static class in Logger.cs
    {
        byte[] byteArray = Encoding.ASCII.GetBytes(ex.Message);
        string fileName = Guid.NewGuid().ToString() + ".txt";
        string storageAccountName = ConfigurationManager.AppSettings["storageAccountName"].ToString();
        string storageContainerName = ConfigurationManager.AppSettings["storageContainerName"].ToString();

        using (MemoryStream stream = new MemoryStream(byteArray))
        {

            StreamToCloudStorageAsync(storageAccountName, storageContainerName, stream, fileName).GetAwaiter().GetResult();

        }
    }

    async static Task StreamToCloudStorageAsync(string accountName, string containerName, Stream sourceStream, string destinationFileName) // static class in Logger.cs
    {
        // Construct the blob container endpoint from the arguments.
        string containerEndpoint = string.Format("https://{0}.blob.core.windows.net/{1}",
                                                accountName,
                                                containerName);

        // Get a credential and create a client object for the blob container.
        BlobContainerClient containerClient = new BlobContainerClient(new Uri(containerEndpoint), new DefaultAzureCredential());

        // Create the container if it does not exist.
        await containerClient.CreateIfNotExistsAsync();

        var blobClient = containerClient.GetBlobClient(destinationFileName);

        // Upload text to a new block blob.
        var blob = await blobClient.UploadAsync(sourceStream);
    }

Any ideas why this hangs?

1

1 Answers

0
votes

.GetAwaiter().GetResult(); is a blocking your code and is leading to deadlocks in your code. See also this blog post.

You should make AddLog return a Task and await the call to StreamToCloudStorageAsync:

internal static Task AddLogAsync(Exception ex, string empty) // static class in Logger.cs
    {
        byte[] byteArray = Encoding.ASCII.GetBytes(ex.Message);
        string fileName = Guid.NewGuid().ToString() + ".txt";
        string storageAccountName = ConfigurationManager.AppSettings["storageAccountName"].ToString();
        string storageContainerName = ConfigurationManager.AppSettings["storageContainerName"].ToString();

        using (MemoryStream stream = new MemoryStream(byteArray))
        {

            await StreamToCloudStorageAsync(storageAccountName, storageContainerName, stream, fileName);

        }
    }

Then refactor the Application_Error event handler to proper await the async call:

    protected async void Application_Error(object sender, EventArgs e) //Global.asax
    {
        Exception ex = Server.GetLastError();

        await Logger.AddLogAsync(ex, string.Empty);
        if (ex.InnerException != null) 
            await Logger.AddLogAsync(ex.InnerException, string.Empty);
        this.Response.Redirect("Error.aspx", true);
    }

Normally one should avoid using async void but event handler are an exception.

Bonus

Your code only logs the exception and first inner exception if available.

        await Logger.AddLogAsync(ex, string.Empty);
        if (ex.InnerException != null) 
            await Logger.AddLogAsync(ex.InnerException, string.Empty);

If you want to log all inner exceptions try

while(ex != null)
{
    await Logger.AddLogAsync(ex, string.Empty);
    ex = ex.InnerException
}