18

Im really new to Windows Azure development and have a requirement to store some data in a windows azure storage table.

This table will really only exist to provide a quick lookup mechanism to some files which are located on azure storage drive.

Therefore I was planning on populating this table at application start up (ie in web application global application start up)

Rather than trying to maintain this table for changes the changes that could occur to the drive while the application is not running. Or as this drive is just a vhd of resources, we may well occasionally upload a new vhd.

So rather than the hassle of trying to maintain this. it is sufficient that this table be rebuild upon each application start.

I started to put together some code to check if the table already exists , and if it does delete it, and then recreate a new table.

var storageAccount = CloudStorageAccount.Parse(ConfigurationManager.ConnectionStrings["AzureStorage"].ConnectionString);
var tableClient = storageAccount.CreateCloudTableClient();
var rmsTable = tableClient.GetTableReference("ResourceManagerStorage");
rmsTable.DeleteIfExists();
rmsTable.Create();

I had expected this would not work. And I get the following error:

The remote server returned an error: (409) Conflict. 

HTTP/1.1 409 Conflict
Cache-Control: no-cache
Transfer-Encoding: chunked
Server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: c6baf92e-de47-4a6d-82b3-4faec637a98c
x-ms-version: 2012-02-12
Date: Tue, 19 Mar 2013 17:26:25 GMT

166
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<error xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
  <code>TableBeingDeleted</code>
  <message xml:lang="en-US">The specified table is being deleted. Try operation later.
RequestId:c6baf92e-de47-4a6d-82b3-4faec637a98c
Time:2013-03-19T17:26:26.2612698Z</message>
</error>
0

What is the correct way of doing this? Is there an event which can be subscribed to to let you know when the table as been deleted? ANy other suggestions on the best way to implement this?

Luke Girvin
  • 13,221
  • 9
  • 64
  • 84
Kramer00
  • 1,167
  • 3
  • 12
  • 34

2 Answers2

30

From MSDN: "Note that deleting a table is likely to take at least 40 seconds to complete. If an operation is attempted against the table while it was being deleted, the service returns status code 409 (Conflict), with additional error information indicating that the table is being deleted."

The only way to deal with this is to create a table with a different name. This could be as simple as appending a timestamp or GUID to your name. Just be careful to clean up your garbage.

IngisKahn
  • 859
  • 6
  • 11
  • Alternatively, you could wait and retry until the Create command succeeds – Igorek Mar 19 '13 at 20:02
  • 3
    Depending on the size of the table and many other factors, that could be a very long time. – IngisKahn Mar 19 '13 at 20:14
  • That was the best way to implement retry logic I've found: http://stackoverflow.com/a/1563234/2186567 – douglaslps Dec 05 '13 at 18:57
  • 1
    In 2015, same issue. My solution: I keep a metadata table around that keeps track of the versions of each of my tables. All queries access the metadata table first to see which table to load first (X1, X2...Xn). On delete/recreate, I load to new, update metadata table, then delete old. – David Betz Sep 28 '15 at 21:19
  • 3
    This is a pain for automated environment scripts that tear down and recreate dbs etc. Probably just need to keep trying with a wait in between until it works. – richard Oct 25 '16 at 06:57
  • 1
    Worth noting that I had this problem after deleting a queue (rather than a table) – Fijjit Jan 11 '17 at 13:09
15

If you need to use the same table name you can use an extension method:

public static class StorageExtensions
{
    #region Non-async

    public static bool SafeCreateIfNotExists(this CloudTable table, TimeSpan? timeout, TableRequestOptions requestOptions = null, OperationContext operationContext = null)
    {
        Stopwatch sw = Stopwatch.StartNew();
        if (timeout == null) timeout = TimeSpan.FromSeconds(41); // Assuming 40 seconds max time, and then some.
        do
        {
            if (sw.Elapsed > timeout.Value) throw new TimeoutException("Table was not deleted within the timeout.");

            try
            {
                return table.CreateIfNotExists(requestOptions, operationContext);
            }
            catch (StorageException e) when(IsTableBeingDeleted(e))
            {
                Thread.Sleep(1000);
            }
        } while (true);
    }

    #endregion

    #region Async

    public static async Task<bool> SafeCreateIfNotExistsAsync(this CloudTable table, TimeSpan? timeout, TableRequestOptions requestOptions = null, OperationContext operationContext = null, CancellationToken cancellationToken = default)
    {
        Stopwatch sw = Stopwatch.StartNew();
        if (timeout == null) timeout = TimeSpan.FromSeconds(41); // Assuming 40 seconds max time, and then some.
        do
        {
            if (sw.Elapsed > timeout.Value) throw new TimeoutException("Table was not deleted within the timeout.");

            try
            {
                return await table.CreateIfNotExistsAsync(requestOptions, operationContext, cancellationToken).ConfigureAwait(false);
            }
            catch (StorageException e) when(IsTableBeingDeleted(e))
            {
                // The table is currently being deleted. Try again until it works.
                await Task.Delay(1000);
            }
        } while (true);
    }

    #endregion

    private static bool IsTableBeingDeleted(StorageException e)
    {
        return
            e.RequestInformation.HttpStatusCode == 409
            &&
            e.RequestInformation.ExtendedErrorInformation.ErrorCode.Equals( TableErrorCodeStrings.TableBeingDeleted );
    }
}

WARNING! Be careful when you use this approach, because it blocks the thread. And it can go to the dead loop if third-party service (Azure) keeps generating these errors. The reason for this could be table locking, subscription expiration, service unavailability, etc.

Dai
  • 141,631
  • 28
  • 261
  • 374
huha
  • 4,053
  • 2
  • 29
  • 47
  • 5
    Wow, that's NOT a great idea. Usually I try to avoid while(true) when the logic depends on third-party services. while(retryCnt > 0) would be better here. – Roman Pushkin May 21 '15 at 17:23
  • 1
    Of course you can pass some timeout parameter or hard code some timeout. But what value would you suggest? 1 minute, 2 minutes, 20 minutes? IngisKahn said "... is likely to take at least 40 seconds to complete." so you need some high value and you never know if the process would have finished if you waited one second longer. If the service is not available or another problem exists I can´t imagine that MS returns the same error TableErrorCodeStrings.TableBeingDeleted. So if someone adds a timeout i`d suggest a rather high value. – huha May 22 '15 at 07:59
  • You can make the method async and replace `Thread.Sleep(2000)` with `await Task.Delay(2000)` - the thread won't be blocked this way. – UserControl Feb 02 '18 at 17:33