Example #1
0
            private async Task CheckpointAsync(ProcessorPartitionContext context)
            {
                bool checkpointed = false;

                if (_batchCheckpointFrequency == 1)
                {
                    await _checkpointer.CheckpointAsync(context).ConfigureAwait(false);

                    checkpointed = true;
                }
                else
                {
                    // only checkpoint every N batches
                    if (++_batchCounter >= _batchCheckpointFrequency)
                    {
                        _batchCounter = 0;
                        await _checkpointer.CheckpointAsync(context).ConfigureAwait(false);

                        checkpointed = true;
                    }
                }
                if (checkpointed)
                {
                    _logger.LogDebug(GetOperationDetails(context, "CheckpointAsync"));
                }
            }
Example #2
0
        /// <inheritdoc />
        public override sealed async Task PartitionStopAsync(ProcessorPartitionContext partitionContext, ProcessingStoppedReason reason, CancellationToken cancellationToken)
        {
            await base.PartitionStopAsync(partitionContext, reason, cancellationToken).ConfigureAwait(false);

            using (Logger.BeginScope("Processor stopping"))
            {
                if (_checkpointTo == null)
                {
                    Logger.LogInformation("Shutting down but no checkpoint data available on partition {partitionId}", partitionContext.PartitionId);
                }
                else if (reason == ProcessingStoppedReason.OwnershipLost)
                {
                    Logger.LogWarning("Cannot checkpoint, ownership was lost for partition {partitionId}, last known sequence number: {checkpointTo}", partitionContext.PartitionId, _checkpointTo.SequenceNumber);
                }
                else
                {
                    try
                    {
                        Logger.LogInformation("Graceful shutdown on partition {partitionId}, forcing checkpoint to sequence number {sequenceNumber}", partitionContext.PartitionId, _checkpointTo.SequenceNumber);

                        var tokenSource       = new CancellationTokenSource(3000);
                        var checkpointSuccess = await _checkpointPolicy.CheckpointAsync(_checkpointTo, true, tokenSource.Token).ConfigureAwait(false);

                        Logger.LogInformation("Completed checkpoint on partition {partitionId} result {result}", partitionContext.PartitionId, checkpointSuccess);
                    }
                    catch (Exception e)
                    {
                        Logger.LogError(e, "Error checkpointing on partition {partitionId} during graceful shutdown, duplicate data expected", partitionContext.PartitionId);
                    }
                }

                var derivedTokenSource = new CancellationTokenSource(3000);
                await ProcessorStoppingAsync(reason, derivedTokenSource.Token).ConfigureAwait(false);
            }
        }
Example #3
0
            public Task ProcessErrorAsync(ProcessorPartitionContext context, Exception error)
            {
                string errorDetails = $"Processing error (Partition Id: '{context.PartitionId}', Owner: '{context.Owner}', EventHubPath: '{context.EventHubPath}').";

                Utility.LogException(error, errorDetails, _logger);

                return(Task.CompletedTask);
            }
Example #4
0
            public Task CloseAsync(ProcessorPartitionContext context, ProcessingStoppedReason reason)
            {
                // signal cancellation for any in progress executions
                _cts.Cancel();

                _logger.LogDebug(GetOperationDetails(context, $"CloseAsync, {reason.ToString()}"));
                return(Task.CompletedTask);
            }
Example #5
0
        /// <inheritdoc />
        public virtual Task PartitionInitializeAsync(ProcessorPartitionContext partitionContext, CancellationToken cancellationToken)
        {
            using (Logger.BeginScope("OnInitialize"))
            {
                Logger.LogInformation("Initializing partition {partitionId}", partitionContext.PartitionId);
            }

            return(Task.CompletedTask);
        }
Example #6
0
        /// <inheritdoc />
        public async Task PartitionProcessAsync(IEnumerable <EventData> events, ProcessorPartitionContext partitionContext, CancellationToken cancellationToken)
        {
            using (Logger.BeginScope("Processing Event Batch"))
            {
                if (events != null)
                {
                    try
                    {
                        if (!PoisonCheckCompleted)
                        {
                            // Check if the are any evens in the list
                            var messageEnumerator = events.GetEnumerator();
                            var hasMessages       = messageEnumerator.MoveNext();

                            if (hasMessages)
                            {
                                if (await IsMessagePoisoned(messageEnumerator.Current, cancellationToken).ConfigureAwait(false) ?? false)
                                {
                                    Logger.LogWarning("Removing poison message, providing remainder of batch on partition {partitionId} for processing sequence number {sequenceNumber}.", partitionContext.PartitionId, messageEnumerator.Current.SequenceNumber);
                                    var updatedList = events.ToList();
                                    updatedList.RemoveAt(0);
                                    events = updatedList;
                                    Logger.LogWarning("Removed poison message, providing remainder of batch on partition {partitionId} for processing {count} message remaining.", partitionContext.PartitionId, updatedList.Count);
                                }
                            }
                        }

                        Logger.LogDebug("Starting custom processing of batch for partition {partitionId}", partitionContext.PartitionId);
                        await ProcessBatchAsync(events, cancellationToken).ConfigureAwait(false);

                        if (_checkpointTo != null)
                        {
                            Logger.LogDebug("Starting policy checkpoint for partition {partitionId}", partitionContext.PartitionId);
                            var checkpointResult = await _checkpointPolicy.CheckpointAsync(_checkpointTo, false, cancellationToken).ConfigureAwait(false);

                            Logger.LogDebug("Completed policy checkpoint for partition {partitionId}, result: {result}", partitionContext.PartitionId, checkpointResult);
                        }
                    }
                    catch (Exception e)
                    {
                        Logger.LogError(e, "Error raised by customer batch processor on partition {partitionId}", partitionContext.PartitionId);
                        throw;
                    }
                }
                else if (_checkpointTo != default && _checkpointTo.SequenceNumber > _checkpointPolicy.SequenceNumber)
                {
                    if (_checkpointTo != null)
                    {
                        Logger.LogDebug("No events received, starting policy checkpoint for partition {partitionId}", partitionContext.PartitionId);
                        var checkpointResult = await _checkpointPolicy.CheckpointAsync(_checkpointTo, false, cancellationToken).ConfigureAwait(false);

                        Logger.LogDebug("Completed policy checkpoint for partition {partitionId}, result: {result}", partitionContext.PartitionId, checkpointResult);
                    }
                }
            }
        }
Example #7
0
        /// <inheritdoc />
        public virtual Task PartitionHandleErrorAsync(Exception exception, ProcessorPartitionContext partitionContext, string operationDescription, CancellationToken cancellationToken)
        {
            using (Logger.BeginScope("OnError"))
            {
                ErrorCounter.Increment();
                Logger.LogError(exception, "Erron partition {partitionId}, {operationDescription}", partitionContext.PartitionId, operationDescription);
            }

            return(Task.CompletedTask);
        }
Example #8
0
        /// <inheritdoc />
        public override sealed async Task InitializeAsync(ILogger logger, IMetricFactory metricFactory, ProcessorPartitionContext partitionContext)
        {
            _metricFactory    = metricFactory;
            _partitionContext = partitionContext;

            await base.InitializeAsync(logger, metricFactory, partitionContext).ConfigureAwait(false);

            await _checkpointPolicy.InitializeAsync(Logger, metricFactory, partitionContext, CancellationToken.None).ConfigureAwait(false);

            await InitializeInstanceAsync().ConfigureAwait(false);
        }
Example #9
0
        /// <inheritdoc />
        public virtual Task PartitionStopAsync(ProcessorPartitionContext partitionContext, ProcessingStoppedReason reason, CancellationToken cancellationToken)
        {
            using (Logger.BeginScope("OnStop"))
            {
                Logger.LogInformation("Stopping partition {partitionId}, reason {reason}", partitionContext.PartitionId, Enum.GetName(typeof(ProcessingStoppedReason), reason));

                if (reason == ProcessingStoppedReason.Shutdown)
                {
                    Logger.LogInformation("Checkpointing in graceful shutdown for partition {partitionId}", partitionContext.PartitionId, Enum.GetName(typeof(ProcessingStoppedReason), reason));
                }
            }

            return(Task.CompletedTask);
        }
Example #10
0
            private static string GetOperationDetails(ProcessorPartitionContext context, string operation)
            {
                StringWriter sw = new StringWriter();

                using (JsonTextWriter writer = new JsonTextWriter(sw)
                {
                    Formatting = Formatting.None
                })
                {
                    writer.WriteStartObject();
                    WritePropertyIfNotNull(writer, "operation", operation);
                    writer.WritePropertyName("partitionContext");
                    writer.WriteStartObject();
                    WritePropertyIfNotNull(writer, "partitionId", context.PartitionId);
                    WritePropertyIfNotNull(writer, "owner", context.Owner);
                    WritePropertyIfNotNull(writer, "eventHubPath", context.EventHubPath);
                    writer.WriteEndObject();

                    // Log partition lease
                    if (context.Lease != null)
                    {
                        writer.WritePropertyName("lease");
                        writer.WriteStartObject();
                        WritePropertyIfNotNull(writer, "offset", context.Lease.Offset.ToString(CultureInfo.InvariantCulture));
                        WritePropertyIfNotNull(writer, "sequenceNumber", context.Lease.SequenceNumber.ToString(CultureInfo.InvariantCulture));
                        writer.WriteEndObject();
                    }

                    // Log RuntimeInformation if EnableReceiverRuntimeMetric is enabled
                    if (context.LastEnqueuedEventProperties != null)
                    {
                        writer.WritePropertyName("lastEnquedEventProperties");
                        writer.WriteStartObject();
                        WritePropertyIfNotNull(writer, "offset", context.LastEnqueuedEventProperties.Value.Offset?.ToString(CultureInfo.InvariantCulture));
                        WritePropertyIfNotNull(writer, "sequenceNumber", context.LastEnqueuedEventProperties.Value.SequenceNumber?.ToString(CultureInfo.InvariantCulture));
                        WritePropertyIfNotNull(writer, "enqueuedTimeUtc", context.LastEnqueuedEventProperties.Value.EnqueuedTime?.ToString("o", CultureInfo.InvariantCulture));
                        writer.WriteEndObject();
                    }
                    writer.WriteEndObject();
                }
                return(sw.ToString());
            }
Example #11
0
        /// <inheritdoc />
        public virtual Task <bool> InitializeAsync(ILogger logger, IMetricFactory metricFactory, ProcessorPartitionContext context, CancellationToken cancellationToken)
        {
            Guard.NotNull(nameof(logger), logger);
            Guard.NotNull(nameof(metricFactory), metricFactory);
            Guard.NotNull(nameof(context), context);

            Logger = logger;

            using (Logger.BeginScope("Initializing Policy Manager"))
            {
                MetricFactory = metricFactory;
                Context       = context;

                Logger.LogInformation("Initializing policy manager {name}", Name);

                _checkpointExecutedCounter  = MetricFactory.CreateCounter("cpol-execution-count", "Counts the number of times a checkpoint has been performed", false, new string[0]);
                _checkpointErrorCounter     = MetricFactory.CreateCounter("cpol-error-count", "Counts the number of times a checkpoint has generated an error", false, new string[0]);
                _checkpointRequestedCounter = MetricFactory.CreateCounter("cpol-requested-count", "Counts the number of times a checkpoint has been requested", false, new string[0]);
            }

            return(Task.FromResult(true));
        }
Example #12
0
        /// <summary>
        /// Updates the poison message information and checks the provided message for poison status, This should only be called on the first message received in a batch, all others should always return false
        /// </summary>
        /// <param name="data">The event data to check for poisoned message status</param>
        /// <param name="partitionContext">The partition context associated with the processor</param>
        /// <param name="cancellationToken">A token to monitor for abort requests</param>
        /// <returns>True if the message is safe to process, false if poisoned</returns>
        protected async Task <bool> UpdatePoisonMonitorStatusAsync(EventData data, ProcessorPartitionContext partitionContext, CancellationToken cancellationToken)
        {
            var shouldProcess = false;

            try
            {
                Logger.LogInformation("Partition {partitionId} checking if Sequence Number {sequenceNumber} is poisoned", partitionContext.PartitionId, data.SequenceNumber);

                if (await _poisonMonitor.IsPoisonedMessageAsync(partitionContext.PartitionId, data.SequenceNumber, cancellationToken).ConfigureAwait(false))
                {
                    var handled = false;

                    for (var handleIndex = 0; handleIndex < 3 && !handled; handleIndex++)
                    {
                        try
                        {
                            Logger.LogWarning("Partition {partitionId} found Sequence Number {sequenceNumber} is poisoned", partitionContext.PartitionId, data.SequenceNumber);
                            handled = await HandlePoisonMessageAsync(data, partitionContext, cancellationToken).ConfigureAwait(false);
                        }
                        catch (Exception e)
                        {
                            Logger.LogError(e, "Partition {partitionId} error handling poison message {sequenceNumber}", partitionContext.PartitionId, data.SequenceNumber);
                        }
                    }

                    var checkpointSuccess = false;

                    for (var checkpointAttempt = 0; checkpointAttempt < 3 && !checkpointSuccess; checkpointAttempt++)
                    {
                        // Immediately checkpoint to move this forward and do not process further
                        await _checkpointPolicy.CheckpointAsync(data, true, cancellationToken).ConfigureAwait(false);

                        checkpointSuccess = true;
                    }
                }
                else
                {
                    Logger.LogInformation("Partition {partitionId} Sequence Number {sequenceNumber} is not poisoned", partitionContext.PartitionId, data.SequenceNumber);

                    shouldProcess = true;
                    PoisonData poisonData = null;

                    for (var retrieveAttempt = 0; retrieveAttempt < 3 && poisonData == null; retrieveAttempt++)
                    {
                        try
                        {
                            poisonData = await _poisonMonitor.GetPoisonDataAsync(partitionContext.PartitionId, cancellationToken).ConfigureAwait(false);
                        }
                        catch (Exception e)
                        {
                            Logger.LogError(e, "Error retrieving poison message data for partition {partitionId}", partitionContext.PartitionId);
                        }
                    }

                    if (poisonData.SequenceNumber == -1)
                    {
                        poisonData.SequenceNumber = data.SequenceNumber;
                        poisonData.ReceiveCount   = 0;
                    }
                    else if (poisonData.SequenceNumber == data.SequenceNumber)
                    {
                        poisonData.ReceiveCount = poisonData.ReceiveCount + 1;
                    }
                    else
                    {
                        poisonData.ReceiveCount   = 1;
                        poisonData.SequenceNumber = data.SequenceNumber;
                    }

                    var updateSuccess = false;

                    for (var retrieveAttempt = 0; retrieveAttempt < 3 && !updateSuccess; retrieveAttempt++)
                    {
                        try
                        {
                            updateSuccess = await _poisonMonitor.UpdatePoisonDataAsync(poisonData, cancellationToken).ConfigureAwait(false);
                        }
                        catch (Exception e)
                        {
                            Logger.LogError(e, "Error updating poison message data for partition {partitionId}", partitionContext.PartitionId);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Logger.LogError(e, "Unknown error cannot in poison message handling for partition {partitionId} with sequence Number {sequenceNumber}, ignoring message", partitionContext.PartitionId, data.SequenceNumber);
            }

            return(shouldProcess);
        }
 /// <inheritdoc />
 protected override Task <bool> HandlePoisonMessageAsync(EventData data, ProcessorPartitionContext partitionContext, CancellationToken cancellationToken)
 {
     // Perform custom processing here for poisoned message, e.g. store to raw store
     return(base.HandlePoisonMessageAsync(data, partitionContext, cancellationToken));
 }
Example #14
0
 public Task OpenAsync(ProcessorPartitionContext context)
 {
     _logger.LogDebug(GetOperationDetails(context, "OpenAsync"));
     return(Task.CompletedTask);
 }
Example #15
0
            public async Task ProcessEventsAsync(ProcessorPartitionContext context, IEnumerable <EventData> messages)
            {
                var triggerInput = new EventHubTriggerInput
                {
                    Events           = messages.ToArray(),
                    PartitionContext = context
                };

                TriggeredFunctionData input = null;

                if (_singleDispatch)
                {
                    // Single dispatch
                    int         eventCount      = triggerInput.Events.Length;
                    List <Task> invocationTasks = new List <Task>();
                    for (int i = 0; i < eventCount; i++)
                    {
                        if (_cts.IsCancellationRequested)
                        {
                            break;
                        }

                        EventHubTriggerInput eventHubTriggerInput = triggerInput.GetSingleEventTriggerInput(i);
                        input = new TriggeredFunctionData
                        {
                            TriggerValue   = eventHubTriggerInput,
                            TriggerDetails = eventHubTriggerInput.GetTriggerDetails(context)
                        };

                        Task task = TryExecuteWithLoggingAsync(input, triggerInput.Events[i]);
                        invocationTasks.Add(task);
                    }

                    // Drain the whole batch before taking more work
                    if (invocationTasks.Count > 0)
                    {
                        await Task.WhenAll(invocationTasks).ConfigureAwait(false);
                    }
                }
                else
                {
                    // Batch dispatch
                    input = new TriggeredFunctionData
                    {
                        TriggerValue   = triggerInput,
                        TriggerDetails = triggerInput.GetTriggerDetails(context)
                    };

                    using (_logger.BeginScope(GetLinksScope(triggerInput.Events)))
                    {
                        await _executor.TryExecuteAsync(input, _cts.Token).ConfigureAwait(false);
                    }
                }

                // Checkpoint if we processed any events.
                // Don't checkpoint if no events. This can reset the sequence counter to 0.
                // Note: we intentionally checkpoint the batch regardless of function
                // success/failure. EventHub doesn't support any sort "poison event" model,
                // so that is the responsibility of the user's function currently. E.g.
                // the function should have try/catch handling around all event processing
                // code, and capture/log/persist failed events, since they won't be retried.
                if (messages.Any())
                {
                    context.CheckpointEvent = messages.Last();
                    await CheckpointAsync(context).ConfigureAwait(false);
                }
            }
Example #16
0
        /// <inheritdoc />
        public virtual Task InitializeAsync(ILogger logger, IMetricFactory metricFactory, ProcessorPartitionContext partitionContext)
        {
            Logger = logger;

            using (Logger.BeginScope("Initialize batch processor"))
            {
                Logger.LogInformation("Initializing batch processor for partition {partitionId}", partitionContext.PartitionId);
                MessageCounter = metricFactory.CreateCounter($"hcp-message-count-{partitionContext.PartitionId}", "The number of messages that have been processed by partition {partitionContext.PartitionId}", false, new string[0]);
                ErrorCounter   = metricFactory.CreateCounter($"hcp-error-count-{partitionContext.PartitionId}", "The number of errors that have been processed by partition {partitionContext.PartitionId}", false, new string[0]);
            }

            return(Task.CompletedTask);
        }
Example #17
0
        /// <summary>
        /// Implement to provide custom handling of poison messages, by default it will be logged and disposed of
        /// </summary>
        /// <param name="data">The message that is poisoned</param>
        /// <param name="partitionContext">A partition context associated with the current processor</param>
        /// <param name="cancellationToken">A token to monitor for abort and cancellation requests</param>
        /// <returns>True if successfully completed the handling. If it fails the handler will be invoked again to a maximum of 3 times total</returns>
        protected virtual Task <bool> HandlePoisonMessageAsync(EventData data, ProcessorPartitionContext partitionContext, CancellationToken cancellationToken)
        {
            Logger.LogWarning("Partition {partitionId}, consider poison message Sequence Number {sequenceNumber} handled", partitionContext.PartitionId, data.SequenceNumber);

            return(Task.FromResult(true));
        }
Example #18
0
 async Task ICheckpointer.CheckpointAsync(ProcessorPartitionContext context)
 {
     await context.CheckpointAsync().ConfigureAwait(false);
 }