/// <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> /// /// <remarks> /// If overridden, the base class implementation must be explicitly called in order to make the event processor start /// running. /// </remarks> /// public virtual async Task StartAsync() { if (ActiveLoadBalancingTask == null) { await RunningTaskSemaphore.WaitAsync().ConfigureAwait(false); try { if (ActiveLoadBalancingTask == null) { // We expect the token source to be null, but we are playing safe. RunningTaskTokenSource?.Cancel(); RunningTaskTokenSource = new CancellationTokenSource(); // Start the main running task. It is responsible for managing the active partition processing tasks and // for partition load balancing among multiple event processor instances. ActiveLoadBalancingTask = RunAsync(RunningTaskTokenSource.Token); } } finally { RunningTaskSemaphore.Release(); } } }
/// <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(); } } }
/// <summary> /// Stops the partition pump. In case it hasn't been started, nothing happens. /// </summary> /// /// <param name="reason">The reason why the partition pump is being closed.</param> /// /// <returns>A task to be resolved on when the operation has completed.</returns> /// private async Task StopAsync(PartitionProcessorCloseReason reason) { 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 InnerConsumer.CloseAsync().ConfigureAwait(false); InnerConsumer = null; await PartitionProcessor.CloseAsync(reason).ConfigureAwait(false); } } finally { RunningTaskSemaphore.Release(); } } }
/// <summary> /// Starts the partition pump. 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) { // We expect the token source to be null, but we are playing safe. RunningTaskTokenSource?.Cancel(); RunningTaskTokenSource = new CancellationTokenSource(); InnerConsumer = InnerClient.CreateConsumer(ConsumerGroup, Context.PartitionId, Options.InitialEventPosition); // In case an exception is encountered while partition processor is initializing, don't catch it // and let the event processor handle it. The inner consumer hasn't connected to the service yet, // so there's no need to close it. await PartitionProcessor.InitializeAsync(Context).ConfigureAwait(false); // Before closing, the running task will set the close reason in case of failure. When something // unexpected happens and it's not set, the default value (Unknown) is kept. RunningTask = RunAsync(RunningTaskTokenSource.Token); } } finally { RunningTaskSemaphore.Release(); } } }
/// <summary> /// Stops the partition pump. In case it isn't running, 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 { // In case the pump has failed, don't catch the unhandled exception and let the caller handle it. await RunningTask.ConfigureAwait(false); } finally { RunningTask = null; await InnerConsumer.CloseAsync().ConfigureAwait(false); } } } finally { RunningTaskSemaphore.Release(); } } }
/// <summary> /// Starts the partition pump. 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(); InnerConsumer = InnerClient.CreateConsumer(ConsumerGroup, PartitionId, Options.InitialEventPosition); await PartitionProcessor.InitializeAsync().ConfigureAwait(false); RunningTask = RunAsync(RunningTaskTokenSource.Token); } } finally { RunningTaskSemaphore.Release(); } } }
/// <summary> /// Starts the partition pump. 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) { // We expect the token source to be null, but we are playing safe. RunningTaskTokenSource?.Cancel(); RunningTaskTokenSource = new CancellationTokenSource(); InnerConsumer = new EventHubConsumerClient(ConsumerGroup, Context.PartitionId, StartingPosition, Connection); RunningTask = RunAsync(RunningTaskTokenSource.Token); } } finally { RunningTaskSemaphore.Release(); } } }
/// <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 virtual async Task StartAsync() { if (RunningTask == null) { await RunningTaskSemaphore.WaitAsync().ConfigureAwait(false); try { if (RunningTask == null) { // We expect the token source to be null, but we are playing safe. RunningTaskTokenSource?.Cancel(); RunningTaskTokenSource = new CancellationTokenSource(); // Initialize our empty ownership dictionary. InstanceOwnership = new Dictionary <string, PartitionOwnership>(); // Start the main running task. It is resposible for managing the partition pumps and for partition // load balancing among multiple event processor instances. RunningTask = RunAsync(RunningTaskTokenSource.Token); } } finally { RunningTaskSemaphore.Release(); } } }
/// <summary> /// Stops the event processor. In case it isn't running, nothing happens. /// </summary> /// /// <returns>A task to be resolved on when the operation has completed.</returns> /// /// <remarks> /// If overridden, the base class implementation must be explicitly called in order to make the event processor stop /// running. /// </remarks> /// public virtual async Task StopAsync() { if (ActiveLoadBalancingTask != null) { await RunningTaskSemaphore.WaitAsync().ConfigureAwait(false); try { if (ActiveLoadBalancingTask != null) { // Cancel the current running task. RunningTaskTokenSource.Cancel(); RunningTaskTokenSource = null; // Now that a cancellation request has been issued, wait for the running task to finish. In case something // unexpected happened and it stopped working midway, this is the moment we expect to catch an exception. try { await ActiveLoadBalancingTask.ConfigureAwait(false); } catch (Exception ex) when(ex is TaskCanceledException || ex is OperationCanceledException) { // Nothing to do here. These exceptions are expected. } catch (Exception) { // TODO: delegate the exception handling to an Exception Callback. Instead of delegating it to the handler, // should we surface it? } // Now that the task has finished, clean up what is left. Stop and remove every partition processing task that is // still running and clear our dictionaries. ActivePartitionProcessors, ActivePartitionProcessorTokenSources and // PartitionContexts are already cleared by the StopPartitionProcessingIfRunningAsync method. await Task.WhenAll(ActivePartitionProcessors.Keys .Select(partitionId => StopPartitionProcessingIfRunningAsync(partitionId, ProcessingStoppedReason.Shutdown))) .ConfigureAwait(false); InstanceOwnership.Clear(); // TODO: once IsRunning is implemented, update the following comment. // We need to wait until all tasks have stopped before making the load balancing task null. If we did it sooner, we // would have a race condition where the user could update the processing handlers while some pumps are still running. ActiveLoadBalancingTask = null; } } finally { RunningTaskSemaphore.Release(); } } }
/// <summary> /// Stops the partition pump. In case it isn't running, nothing happens. /// </summary> /// /// <param name="reason">The reason why the processing for the associated partition is being stopped. In case it's <c>null</c>, the internal close reason set by this pump is used.</param> /// /// <returns>A task to be resolved on when the operation has completed.</returns> /// public async Task StopAsync(CloseReason?reason) { if (RunningTask != null) { await RunningTaskSemaphore.WaitAsync().ConfigureAwait(false); try { if (RunningTask != null) { RunningTaskTokenSource.Cancel(); RunningTaskTokenSource = null; try { // RunningTask is only expected to fail when the event processor throws while processing // an error, but unforeseen scenarios might happen. await RunningTask.ConfigureAwait(false); } catch (Exception) { // TODO: delegate the exception handling to an Exception Callback. } RunningTask = null; // It's important to close the consumer as soon as possible. Failing to do so multiple times // would make it impossible to create more consumers for the associated partition as there's a // limit per client. await InnerConsumer.CloseAsync().ConfigureAwait(false); // In case an exception is encountered while the processing is stopping, don't catch it and let // the event processor handle it. The pump has no way to guess when a partition was lost or when // a shutdown request was sent to the event processor, so it expects a "reason" parameter to provide // this information. However, in case of pump failure, the external event processor does not have // enough information to figure out what failure reason to use, as this information is only known // by the pump. In this case, we expect the processor-provided reason to be null, and the private // CloseReason is used instead. if (OwnerEventProcessor.ProcessingForPartitionStoppedAsync != null) { var stopContext = new PartitionProcessingStoppedContext(Context, reason ?? CloseReason); await OwnerEventProcessor.ProcessingForPartitionStoppedAsync(stopContext).ConfigureAwait(false); } } } finally { RunningTaskSemaphore.Release(); } } }
/// <summary> /// Stops the event processor. In case it isn't running, nothing happens. /// </summary> /// /// <returns>A task to be resolved on when the operation has completed.</returns> /// public virtual async Task StopAsync() { if (RunningTask != null) { await RunningTaskSemaphore.WaitAsync().ConfigureAwait(false); try { if (RunningTask != null) { // Cancel the current running task. RunningTaskTokenSource.Cancel(); RunningTaskTokenSource = null; // Now that a cancellation request has been issued, wait for the running task to finish. In case something // unexpected happened and it stopped working midway, this is the moment we expect to catch an exception. try { await RunningTask.ConfigureAwait(false); } catch (TaskCanceledException) { // The running task has an inner delay that is likely to throw a TaskCanceledException upon token cancellation. // The task might end up leaving its main loop gracefully by chance, so we won't necessarily reach this part of // the code. } catch (Exception) { // TODO: delegate the exception handling to an Exception Callback. } RunningTask = null; // Now that the task has finished, clean up what is left. Stop and remove every partition pump that is still // running and dispose of our ownership dictionary. InstanceOwnership = null; await Task.WhenAll(PartitionPumps.Keys .Select(partitionId => RemovePartitionPumpIfItExistsAsync(partitionId, PartitionProcessorCloseReason.Shutdown))) .ConfigureAwait(false); } } finally { RunningTaskSemaphore.Release(); } } }
/// <summary> /// Starts the partition pump. 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) { // We expect the token source to be null, but we are playing safe. RunningTaskTokenSource?.Cancel(); RunningTaskTokenSource = new CancellationTokenSource(); // In case an exception is encountered while the processing is initializing, don't catch it // and let the event processor handle it. EventPosition startingPosition = StartingPosition; if (OwnerEventProcessor.InitializeProcessingForPartitionAsync != null) { var initializationContext = new InitializePartitionProcessingContext(Context); await OwnerEventProcessor.InitializeProcessingForPartitionAsync(initializationContext).ConfigureAwait(false); startingPosition = startingPosition ?? initializationContext.DefaultStartingPosition; } InnerConsumer = new EventHubConsumerClient(ConsumerGroup, Context.PartitionId, startingPosition ?? EventPosition.Earliest, Connection); // Before closing, the running task will set the close reason in case of failure. When something // unexpected happens and it's not set, the default value (Unknown) is kept. RunningTask = RunAsync(RunningTaskTokenSource.Token); } } finally { RunningTaskSemaphore.Release(); } } }
/// <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(); } } }