7

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?

Kevin Jensen Petersen
  • 423
  • 2
  • 22
  • 43

1 Answers1

11

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.

Roman Kiss
  • 7,925
  • 1
  • 8
  • 21
  • Question: can the same be achieved by passing a `BlobRequestOptions` object to `AcquireLeaseAsync()`? `var leaseId = await leaseBlob.AcquireLeaseAsync(null, null, null, new BlobRequestOptions() { RetryPolicy = new Microsoft.Azure.Storage.RetryPolicies.LinearRetry() }, default(OperationContext));` Will that work? – Sachin Joseph Feb 21 '22 at 01:28
  • @SachinJoseph The acquiring a Lease blob from multiple consumers (instances) must be done randomly in opposite to the *LinearRetry* policy, where the interval between reties is using a specified fixed time. – Roman Kiss Feb 21 '22 at 15:18
  • Why must it be random? Also cant consumers be placed into a queue and notified when the lease expires? – Benjamin Dec 15 '22 at 22:15