Background: We have an Azure .NET application where we need to register a single "front end" user with multiple back end providers. Since this registration takes longer, we offload it to worker role and there are multiple worker roles. All data is stored in Azure SQL and we're using Entity Framework 5.0 as our ORM. The way we're currently setup, we read from SQL dB => process in worker role code => write/update to SQL dB to flag completion. Essentially I need to solve the traditional "multithreaded + shared data writes" problem but instead of OS scale it's at the cloud scale.
Concern: We have a race condition with multiple workers if the first worker take longer than the visibility timeout. For example, assuming two worker roles, I've marked below how both would read from SQL, think that the processing is still pending and both would proceed. It results in a last-writer-wins race condition and also creates orphaned and extra accounts on the external service providers.
Question: How can I modify this to take care of this situation elegantly? I can alter the data flow or use a per-user "cloud" lock for Mutex. Without trying to constraint anyone's thinking, in the past I speculated having a SQL based cloud lock, but couldn't really get it working in EF5.0. Here I'm trying to explore any answers, SQL based locks or not.
// Read message off Service Bus Queue
// message invisible for 1 min now, worker must finish in 1 min
BrokeredMessage qMsg = queueClient.Receive();
// Extract UserID Guid from message
Guid userProfileId = DeserializeUserIdFromQMsg(qMsg);
// READ PROFILE FROM SQL
UserProfile up = (from x in myAppDbContext.UserProfiles select x).SingleOrDefault(p => p.UserProfileId == userProfileId);
if (up != null)
{
List<Task> allUserRegTasks = new List<Task>();
string firstName = up.FirstName; // <== WORKER ROLE #2 HERE
string lastName = up.LastName;
string emailAddress = up.Email;
// Step 1: Async register User with provider #1, update db
if (String.IsNullOrEmpty(up.Svc1CustId))
{
// <== WORKER ROLE #1 HERE
Svc1RegHelper svc1RegHelper = new Svc1RegHelper();
Task svc1UserRegTask = svc1RegHelper.GetRegisterTask(userProfileId, firstName, lastName, emailAddress);
svc1UserRegTask.Start(); // <== SQL WRITE INSIDE THIS (marks "up.Svc1CustId")
allUserRegTasks.Add(svc1UserRegTask);
}
// Step 2: Async register User with provider #2, update db
if (String.IsNullOrEmpty(up.Svc2CustId))
{
Svc2RegHelper svc2RegHelper = new Svc2RegHelper();
Task svc2UserRegTask = svc2RegHelper.GetRegisterTask(userProfileId, firstName, lastName, emailAddress);
svc2UserRegTask.Start(); // <== SQL WRITE INSIDE THIS (marks "up.Svc2CustId")
allUserRegTasks.Add(svc2UserRegTask);
}
Task.WaitAll(allUserRegTasks.ToArray());
// Step 3: Send confirmation email to user we're ready for them!
// ...
}