async Task ProcessMessage(Message receivedMessage, CancellationToken token) { try { await maxConcurrencySempahore.WaitAsync(token).ConfigureAwait(false); } catch (OperationCanceledException) { // shutting down, semaphore doesn't need to be released because it was never acquired return; } try { IncomingMessage incomingMessage = null; TransportMessage transportMessage = null; var isPoisonMessage = false; try { transportMessage = JsonConvert.DeserializeObject <TransportMessage>(receivedMessage.Body); incomingMessage = await transportMessage.ToIncomingMessage(s3Client, configuration, token).ConfigureAwait(false); } catch (OperationCanceledException) { // shutting down return; } catch (Exception ex) { // Can't deserialize. This is a poison message Logger.Warn($"Treating message with SQS Message Id {receivedMessage.MessageId} as a poison message due to exception {ex}. Moving to error queue."); isPoisonMessage = true; } if (incomingMessage == null || transportMessage == null) { Logger.Warn($"Treating message with SQS Message Id {receivedMessage.MessageId} as a poison message because it could not be converted to an IncomingMessage. Moving to error queue."); isPoisonMessage = true; } if (isPoisonMessage) { await MovePoisonMessageToErrorQueue(receivedMessage).ConfigureAwait(false); return; } if (!IsMessageExpired(receivedMessage, incomingMessage)) { await ProcessMessageWithInMemoryRetries(incomingMessage, token).ConfigureAwait(false); } // Always delete the message from the queue. // If processing failed, the onError handler will have moved the message // to a retry queue. await DeleteMessage(receivedMessage, transportMessage, incomingMessage, token).ConfigureAwait(false); } finally { maxConcurrencySempahore.Release(); } }