Beispiel #1
0
        /// <summary>
        ///   Stops the event processor.  In case it hasn't been started, nothing happens.
        /// </summary>
        ///
        /// <returns>A task to be resolved on when the operation has completed.</returns>
        ///
        public async Task StopAsync()
        {
            if (RunningTask != null)
            {
                await RunningTaskSemaphore.WaitAsync().ConfigureAwait(false);

                try
                {
                    if (RunningTask != null)
                    {
                        RunningTaskTokenSource.Cancel();
                        RunningTaskTokenSource = null;

                        try
                        {
                            await RunningTask.ConfigureAwait(false);
                        }
                        finally
                        {
                            RunningTask = null;
                        }

                        await Task.WhenAll(PartitionPumps.Select(kvp => kvp.Value.StopAsync())).ConfigureAwait(false);
                    }
                }
                finally
                {
                    RunningTaskSemaphore.Release();
                }
            }
        }
Beispiel #2
0
 /// <summary>
 ///   Stops an owned partition pump instance in case it exists.  It is also removed from the pumps dictionary.
 /// </summary>
 ///
 /// <param name="partitionId">The identifier of the Event Hub partition the partition pump is associated with.</param>
 /// <param name="reason">The reason why the partition processor is being closed.</param>
 ///
 /// <returns>A task to be resolved on when the operation has completed.</returns>
 ///
 private async Task RemovePartitionPumpIfItExistsAsync(string partitionId,
                                                       PartitionProcessorCloseReason?reason = null)
 {
     if (PartitionPumps.TryRemove(partitionId, out var pump))
     {
         try
         {
             await pump.StopAsync(reason).ConfigureAwait(false);
         }
         catch (Exception)
         {
             // TODO: delegate the exception handling to an Exception Callback.
         }
     }
 }
Beispiel #3
0
        /// <summary>
        ///   The main loop of an event processor.  It loops through every owned <see cref="PartitionPump" />, checking
        ///   its status and creating a new one if necessary.
        /// </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>
        ///
        /// <remarks>
        ///   The actual goal of this method is to perform load balancing between multiple <see cref="EventProcessor" />
        ///   instances, but this feature is currently out of the scope of the current preview.
        /// </remarks>
        ///
        private async Task RunAsync(CancellationToken cancellationToken)
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                await Task.WhenAll(PartitionPumps
                                   .Where(kvp => !kvp.Value.IsRunning)
                                   .Select(async kvp =>
                {
                    try
                    {
                        await kvp.Value.StopAsync();
                    }
                    catch (Exception)
                    {
                        // We're catching every possible unhandled exception that may have happened during Partition Pump execution.
                        // TODO: delegate the exception handling to an Exception Callback.
                    }

                    var partitionId = kvp.Key;

                    var partitionContext  = new PartitionContext(InnerClient.EventHubName, ConsumerGroup, partitionId);
                    var checkpointManager = new CheckpointManager(partitionContext, Manager, Identifier);

                    var partitionProcessor = PartitionProcessorFactory(partitionContext, checkpointManager);

                    var partitionPump = new PartitionPump(InnerClient, ConsumerGroup, partitionId, partitionProcessor, Options);
                    PartitionPumps.TryUpdate(partitionId, partitionPump, partitionPump);

                    await partitionPump.StartAsync();
                })).ConfigureAwait(false);

                try
                {
                    // Wait 1 second before the next verification.

                    await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
                }
                catch (TaskCanceledException) { }
            }
        }
Beispiel #4
0
        /// <summary>
        ///   Starts the event processor.  In case it's already running, nothing happens.
        /// </summary>
        ///
        /// <returns>A task to be resolved on when the operation has completed.</returns>
        ///
        public async Task StartAsync()
        {
            if (RunningTask == null)
            {
                await RunningTaskSemaphore.WaitAsync().ConfigureAwait(false);

                try
                {
                    if (RunningTask == null)
                    {
                        RunningTaskTokenSource?.Cancel();
                        RunningTaskTokenSource = new CancellationTokenSource();

                        PartitionPumps.Clear();

                        var partitionIds = await InnerClient.GetPartitionIdsAsync().ConfigureAwait(false);

                        await Task.WhenAll(partitionIds
                                           .Select(partitionId =>
                        {
                            var partitionContext  = new PartitionContext(InnerClient.EventHubName, ConsumerGroup, partitionId);
                            var checkpointManager = new CheckpointManager(partitionContext, Manager, Identifier);

                            var partitionProcessor = PartitionProcessorFactory(partitionContext, checkpointManager);

                            var partitionPump = new PartitionPump(InnerClient, ConsumerGroup, partitionId, partitionProcessor, Options);
                            PartitionPumps.TryAdd(partitionId, partitionPump);

                            return(partitionPump.StartAsync());
                        })).ConfigureAwait(false);

                        RunningTask = RunAsync(RunningTaskTokenSource.Token);
                    }
                }
                finally
                {
                    RunningTaskSemaphore.Release();
                }
            }
        }
Beispiel #5
0
        /// <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);
                }
            }
        }