Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Azure Blob Storage | AcquireLeaseAsync, synchronously wait until lock is released

Right now I'm making a HTTP serverless function with Azure Functions and with that I need to make my incoming requests synchronous when accessing a single Blob Storage Item on my Storage Account.

What I want to happen is the following:

// Incoming Request 1

// .. Get blob

await myBlob.AcquireLeaseAsync(TimeSpan.FromMinutes(1), leaseId);

// .. 10 seconds later

await myblob.ReleaseLeaseAsync(AccessCondition.GenerateEmptyCondition());


//
// MEANWHILE
//

// Incoming Request 2 (Happens 1-2 seconds after Request 1)

// .. Get blob    
// .. -- Wait until Lease is released from Request 1 --


await myBlob.AcquireLeaseAsync(TimeSpan.FromMinutes(1), leaseId);

// Repeat...

However I noticed when I try to get Blob on Request 2, it doesn't synchronously wait until the Blob lease is released from Request 1, it simply just returns an error which says that there's already an active Lease on this blob, and then just continues.

Is it possible to have the above synchronous system in an Azure Function?

like image 395
Kevin Jensen Petersen Avatar asked Jan 27 '23 09:01

Kevin Jensen Petersen


1 Answers

The error (probably a code=409) what you are received is correct when you acquired a lease blob. This is a perfect behavior for serverless distributed model, where more functions (jobs, workers, etc.) want to have an exclusive access on the lease blob.

Only one of them can be a winner and the others must periodically asking again for their acquire lease. It is recommended during this acquiring period use a random waiting time.

The following code snippet shows an example of this "loop logic" in the azure function:

    // Acquireing a Lease blob
    public async static Task<string> LockingEntity(dynamic entity, int leaseTimeInSec = 60, string leaseId = null)
    {
       while (true)
       {
          try
          {
             string lid = await entity.AcquireLeaseAsync(TimeSpan.FromSeconds(leaseTimeInSec), leaseId);
             if (string.IsNullOrEmpty(lid))
                await Task.Delay(TimeSpan.FromMilliseconds(new Random(Guid.NewGuid().GetHashCode()).Next(250, 1000)));
             else
                return lid;
          }
          catch (StorageException ex)
          {
             if (ex.RequestInformation.HttpStatusCode != 409)
                throw;
          }
      }
   }

the usage of the above method in the azure function:

    string leaseId = await LockingEntity(blockBlob);
    if (string.IsNullOrEmpty(leaseId) == false)
    {
       try
       {
          string esp = await blockBlob.DownloadTextAsync(Encoding.UTF8, AccessCondition.GenerateLeaseCondition(leaseId), null, null);
          state = JsonConvert.DeserializeObject<State>(esp);
          // …
       }
       finally
       {
          await blockBlob.ReleaseLeaseAsync(AccessCondition.GenerateLeaseCondition(leaseId));
       }
    }

Also, have a look at my article Using Azure Lease Blob for more patterns, etc. in the serverless event driven distributed architecture.

like image 72
Roman Kiss Avatar answered Feb 01 '23 14:02

Roman Kiss