public async Task <TaskSeriesCommandResult> ExecuteAsync(CancellationToken cancellationToken) { TimeSpan delay; try { UpdateReceipt updateReceipt = await _queue.UpdateMessageAsync(_message.MessageId, _message.PopReceipt, visibilityTimeout : _visibilityTimeout, cancellationToken : cancellationToken).ConfigureAwait(false); _message = _message.Update(updateReceipt); _onUpdateReceipt?.Invoke(updateReceipt); // The next execution should occur after a normal delay. delay = _speedupStrategy.GetNextDelay(executionSucceeded: true); } catch (RequestFailedException exception) { // For consistency, the exceptions handled here should match PollQueueCommand.DeleteMessageAsync. if (exception.IsServerSideError()) { // The next execution should occur more quickly (try to update the visibility before it expires). delay = _speedupStrategy.GetNextDelay(executionSucceeded: false); } if (exception.IsBadRequestPopReceiptMismatch()) { // There's no point to executing again. Once the pop receipt doesn't match, we've permanently lost // ownership. delay = Timeout.InfiniteTimeSpan; } else if (exception.IsNotFoundMessageOrQueueNotFound() || exception.IsConflictQueueBeingDeletedOrDisabled()) { // There's no point to executing again. Once the message or queue is deleted, we've permanently lost // ownership. // For queue disabled, in theory it's possible the queue could be re-enabled, but the scenarios here // are currently unclear. delay = Timeout.InfiniteTimeSpan; } else { throw; } } return(new TaskSeriesCommandResult(wait: Task.Delay(delay, cancellationToken))); }
internal async Task ProcessMessageAsync(QueueMessage message, TimeSpan visibilityTimeout, CancellationToken cancellationToken) { try { if (!await _queueProcessor.BeginProcessingMessageAsync(message, cancellationToken).ConfigureAwait(false)) { return; } FunctionResult result = null; Action <UpdateReceipt> onUpdateReceipt = updateReceipt => { message = message.Update(updateReceipt); }; using (ITaskSeriesTimer timer = CreateUpdateMessageVisibilityTimer(_queue, message, visibilityTimeout, _exceptionHandler, onUpdateReceipt)) { timer.Start(); result = await _triggerExecutor.ExecuteAsync(message, cancellationToken).ConfigureAwait(false); await timer.StopAsync(cancellationToken).ConfigureAwait(false); } // Use a different cancellation token for shutdown to allow graceful shutdown. // Specifically, don't cancel the completion or update of the message itself during graceful shutdown. // Only cancel completion or update of the message if a non-graceful shutdown is requested via _shutdownCancellationTokenSource. await _queueProcessor.CompleteProcessingMessageAsync(message, result, _shutdownCancellationTokenSource.Token).ConfigureAwait(false); } catch (TaskCanceledException) { // Don't fail the top-level task when an inner task cancels. } catch (OperationCanceledException) { // Don't fail the top-level task when an inner task cancels. } catch (Exception exception) { // Immediately report any unhandled exception from this background task. // (Don't capture the exception as a fault of this Task; that would delay any exception reporting until // Stop is called, which might never happen.) #pragma warning disable AZC0103 // Do not wait synchronously in asynchronous scope. _exceptionHandler.OnUnhandledExceptionAsync(ExceptionDispatchInfo.Capture(exception)).GetAwaiter().GetResult(); #pragma warning restore AZC0103 // Do not wait synchronously in asynchronous scope. } }