/// <summary> /// Performs load balancing between multiple <see cref="EventProcessor{T}" /> instances, claiming others' partitions to enforce /// a more equal distribution when necessary. It also manages its own partition pumps and ownership. /// </summary> /// /// <param name="cancellationToken">A <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param> /// /// <returns>A task to be resolved on when the operation has completed.</returns> /// private async Task RunAsync(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { Stopwatch cycleDuration = Stopwatch.StartNew(); // Renew this instance's ownership so they don't expire. await RenewOwnershipAsync().ConfigureAwait(false); // From the storage service provided by the user, obtain a complete list of ownership, including expired ones. We may still need // their eTags to claim orphan partitions. var completeOwnershipList = (await Manager .ListOwnershipAsync(InnerClient.EventHubName, ConsumerGroup) .ConfigureAwait(false)) .ToList(); // Filter the complete ownership list to obtain only the ones that are still active. The expiration time defaults to 30 seconds, // but it may be overriden by a derived class. var activeOwnership = completeOwnershipList .Where(ownership => DateTimeOffset.UtcNow.Subtract(ownership.LastModifiedTime.Value) < OwnershipExpiration); // Dispose of all previous partition ownership instances and get a whole new dictionary. InstanceOwnership = activeOwnership .Where(ownership => ownership.OwnerIdentifier == Identifier) .ToDictionary(ownership => ownership.PartitionId); // Some previously owned partitions might have had their ownership expired or might have been stolen, so we need to stop // the pumps we don't need anymore. await Task.WhenAll(PartitionPumps.Keys .Except(InstanceOwnership.Keys) .Select(partitionId => RemovePartitionPumpIfItExistsAsync(partitionId, PartitionProcessorCloseReason.OwnershipLost))) .ConfigureAwait(false); // Now that we are left with pumps that should be running, check their status. If any has stopped, it means an // unexpected failure has happened, so try closing it and starting a new one. In case we don't have a pump that // should exist, create it. This might happen when pump creation has failed in a previous cycle. await Task.WhenAll(InstanceOwnership .Where(kvp => { if (PartitionPumps.TryGetValue(kvp.Key, out var pump)) { return(!pump.IsRunning); } return(true); }) .Select(kvp => AddOrOverwritePartitionPumpAsync(kvp.Key, kvp.Value.SequenceNumber))) .ConfigureAwait(false); // Find an ownership to claim and try to claim it. The method will return null if this instance was not eligible to // increase its ownership list, if no claimable ownership could be found or if a claim attempt failed. var claimedOwnership = await FindAndClaimOwnershipAsync(completeOwnershipList, activeOwnership).ConfigureAwait(false); if (claimedOwnership != null) { InstanceOwnership[claimedOwnership.PartitionId] = claimedOwnership; await AddOrOverwritePartitionPumpAsync(claimedOwnership.PartitionId, claimedOwnership.SequenceNumber).ConfigureAwait(false); } // Wait the remaining time, if any, to start the next cycle. The total time of a cycle defaults to 10 seconds, // but it may be overriden by a derived class. TimeSpan remainingTimeUntilNextCycle = cycleDuration.Elapsed - LoadBalanceUpdate; if (remainingTimeUntilNextCycle > TimeSpan.Zero) { // If a stop request has been issued, Task.Delay will throw a TaskCanceledException. This is expected and it // will be caught by the StopAsync method. await Task.Delay(remainingTimeUntilNextCycle, cancellationToken).ConfigureAwait(false); } } }
/// <summary> /// Performs load balancing between multiple <see cref="EventProcessor" /> instances, claiming others' partitions to enforce /// a more equal distribution when necessary. It also manages its own partition pumps and ownership. /// </summary> /// /// <param name="cancellationToken">A <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param> /// /// <returns>A task to be resolved on when the operation has completed.</returns> /// private async Task RunAsync(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { Stopwatch cycleDuration = Stopwatch.StartNew(); // Renew this instance's ownership so they don't expire. This method call will fill the InstanceOwnership dictionary // with the renewed ownership information. await RenewOwnershipAsync().ConfigureAwait(false); // Some previously owned partitions might have had their ownership expired or might have been stolen, so we need to stop // the pumps we don't need anymore. await Task.WhenAll(PartitionPumps.Keys .Except(InstanceOwnership.Keys) .Select(partitionId => RemovePartitionPumpIfItExistsAsync(partitionId, PartitionProcessorCloseReason.OwnershipLost))) .ConfigureAwait(false); // Now that we are left with pumps that should be running, check their status. If any has stopped, it means an // unexpected failure has happened, so try closing it and starting a new one. In case we don't have a pump that // should exist, create it. This might happen when pump creation has failed in a previous cycle. await Task.WhenAll(InstanceOwnership.Keys .Where(partitionId => { if (PartitionPumps.TryGetValue(partitionId, out var pump)) { return(!pump.IsRunning); } return(true); }) .Select(partitionId => AddOrOverwritePartitionPumpAsync(partitionId))) .ConfigureAwait(false); // Find an ownership to claim and try to claim it. The method will return null if this instance was not eligible to // increase its ownership list, if no claimable ownership could be found or if a claim attempt failed. var claimedOwnership = await FindAndClaimOwnershipAsync().ConfigureAwait(false); if (claimedOwnership != null) { InstanceOwnership[claimedOwnership.PartitionId] = claimedOwnership; await AddOrOverwritePartitionPumpAsync(claimedOwnership.PartitionId).ConfigureAwait(false); } // Wait the remaining time, if any, to start the next cycle. The total time of a cycle defaults to 10 seconds, // but it may be overriden by a derived class. TimeSpan remainingTimeUntilNextCycle = cycleDuration.Elapsed - LoadBalanceUpdate; if (remainingTimeUntilNextCycle > TimeSpan.Zero) { // If a stop request has been issued, Task.Delay will throw a TaskCanceledException. This is expected and it // will be caught by the StopAsync method. await Task.Delay(remainingTimeUntilNextCycle, cancellationToken).ConfigureAwait(false); } } }