Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1552,6 +1552,15 @@ public async Task<TaskActivityWorkItem> LockNextTaskActivityWorkItem(

using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, this.shutdownSource.Token))
{
try
{
await this.appLeaseManager.WaitForActivityOwnershipAsync(linkedCts.Token);
Comment thread
YunchuWang marked this conversation as resolved.
}
catch (OperationCanceledException)
{
return null;
}

MessageData message = await this.workItemQueue.GetMessageAsync(linkedCts.Token);

if (message == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,13 @@ public class AzureStorageOrchestrationServiceSettings
public TimeSpan MaxQueuePollingInterval { get; set; } = DefaultMaxQueuePollingInterval;

/// <summary>
/// If true, takes a lease on the task hub container, allowing for only one app to process messages in a task hub at a time.
/// If true, only workers whose <see cref="AppName"/> owns the task hub app lease may start new
/// orchestration, entity, or activity work. Workers sharing that app name may process work in
/// parallel. If false, workers from all apps sharing the task hub may process work.
/// Activity ownership is checked before starting each queue receive. Ownership loss does not
/// cancel an activity receive that already started, so it may continue polling and execute a
/// returned activity; the next receive waits for ownership. The activity gate is cooperative,
/// not an atomic or exactly-once ownership boundary, and already dispatched activities continue.
/// </summary>
public bool UseAppLease { get; set; } = true;

Expand Down
68 changes: 68 additions & 0 deletions src/DurableTask.AzureStorage/Partitioning/AppLeaseManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ sealed class AppLeaseManager
readonly Blob appLeaseInfoBlob;
readonly string appLeaseId;
readonly AsyncManualResetEvent shutdownCompletedEvent;
readonly object activityOwnershipLock = new object();

TaskCompletionSource<object> activityOwnershipAvailable;
bool hasActivityOwnership;
bool isLeaseOwner;
int appLeaseIsStarted;
Task renewTask;
Expand Down Expand Up @@ -78,12 +81,71 @@ public AppLeaseManager(

this.isLeaseOwner = false;
this.shutdownCompletedEvent = new AsyncManualResetEvent();
this.activityOwnershipAvailable = CreateActivityOwnershipSignal();
}

internal async Task WaitForActivityOwnershipAsync(
CancellationToken cancellationToken)
{
while (true)
{
Task ownershipAvailableTask;
lock (this.activityOwnershipLock)
{
if (this.hasActivityOwnership)
{
return;
}

ownershipAvailableTask = this.activityOwnershipAvailable.Task;
}

var canceled = new TaskCompletionSource<object>(
TaskCreationOptions.RunContinuationsAsynchronously);
using (cancellationToken.Register(() => canceled.TrySetResult(null)))
{
await Task.WhenAny(ownershipAvailableTask, canceled.Task);
}

cancellationToken.ThrowIfCancellationRequested();
}
}

internal void SetActivityOwnership(bool ownsLease)
{
TaskCompletionSource<object> ownershipAvailable = null;
lock (this.activityOwnershipLock)
{
if (this.hasActivityOwnership == ownsLease)
{
return;
}

this.hasActivityOwnership = ownsLease;
if (ownsLease)
{
ownershipAvailable = this.activityOwnershipAvailable;
}
else
{
this.activityOwnershipAvailable = CreateActivityOwnershipSignal();
}
Comment thread
YunchuWang marked this conversation as resolved.
}

ownershipAvailable?.TrySetResult(null);
}

static TaskCompletionSource<object> CreateActivityOwnershipSignal()
{
return new TaskCompletionSource<object>(
TaskCreationOptions.RunContinuationsAsynchronously);
}

public async Task StartAsync()
{
if (!this.appLeaseIsEnabled)
{
this.SetActivityOwnership(ownsLease: true);
this.starterTokenSource = new CancellationTokenSource();

await Task.Factory.StartNew(() => this.PartitionManagerStarter(this.starterTokenSource.Token));
Expand Down Expand Up @@ -165,6 +227,8 @@ async Task AppLeaseManagerStarter(CancellationToken cancellationToken)

public async Task StopAsync()
{
this.SetActivityOwnership(ownsLease: false);

if (this.starterTokenSource != null)
{
this.starterTokenSource.Cancel();
Expand Down Expand Up @@ -253,6 +317,7 @@ async Task StartAppLeaseAsync()
this.leaseRenewerCancellationTokenSource = new CancellationTokenSource();

await this.partitionManager.StartAsync();
this.SetActivityOwnership(ownsLease: true);
Comment thread
YunchuWang marked this conversation as resolved.

this.shutdownCompletedEvent.Reset();

Expand All @@ -267,6 +332,8 @@ async Task StopAppLeaseAsync()
return;
}

this.SetActivityOwnership(ownsLease: false);

await this.partitionManager.StopAsync();

if (this.renewTask != null)
Expand Down Expand Up @@ -491,6 +558,7 @@ async Task<bool> RenewLeaseAsync()
{
renewed = false;
this.isLeaseOwner = false;
this.SetActivityOwnership(ownsLease: false);

this.settings.Logger.LeaseRenewalFailed(
this.storageAccountName,
Expand Down
Loading
Loading