2

I have a function which pulls messages off a subscription, and forwards them to an HTTP endpoint. If the endpoint is unavailable, an exception is thrown. When this happens, I would like to delay the next attempt of that specific message for a certain amount of time, e.g. 15 minutes. So far, I have found the following solutions:

  • Catch the exception, sleep, then throw. This is a terrible solution, as I will be charged for CPU usage while it is sleeping, and it will affect the throughput of the function.
  • Catch the exception, clone the message, set the ScheduledEnqueueTimeUtc property and add it back to the queue. This is a nicer way, but it resets the delivery count, so an actual problem will never be dead-lettered, and it is resent to all subscriptions, when only one subscriber failed to process it.
  • Catch the exception, and place the message on a storage queue instead. This means maintaining a storage queue to match each subscription, and having two functions instead of one.

What I would ideally like to happen is to catch the exception, and exit the function without releasing the lock on the message. That way, as soon as the lock expires the message will be retried again. However, it seems that after completing successfully, the function calls Complete() on the message, and after an exception is thrown, the function calls Abandon() on the message. Is it possible to bypass this, or to achieve the delay some other way?

Andrew Williamson
  • 8,299
  • 3
  • 34
  • 62
  • If these exceptions don't happen too often, you could create a function with a timer that would only fire every 15 minutes and checks the dead letter queue. – JTaub Apr 18 '18 at 12:49

3 Answers3

2

This is now supported natively through retry policies, which were added to Azure Functions around November 2020 (preview). You can configure the retry policy as fixed-delay or exponential-backoff.

[FunctionName("MyFunction")]
[FixedDelayRetry(10, "00:15:00")]   // retries with a 15-minute delay
public static void Run(
    [ServiceBusTrigger("MyTopic", "MySubscription", Connection = "ServiceBusConnection")] string myQueueItem) 
{
    // Forward message to HTTP endpoint, throwing exception if endpoint unavailable
}
Douglas
  • 53,759
  • 13
  • 140
  • 188
  • 2
    Keep in mind that the ServiceBus engine running the function since it has a SB trigger is unaware of the function retry policy so if you have redelivery setup, the total amount of retry will be (SB max redelivery * function retry policy amount) which is most likely _not_ what you want. – gfache May 09 '22 at 15:29
1

I will address your situation by offering that the flow you are proposing is better handled by a LogicApp over a pure function.

It's quite easy to implement the wait/retry/dequeue next pattern in a LogicApp since this type of flow control is exactly what LogicApps was designed for.

Johns-305
  • 10,908
  • 12
  • 21
  • Yes, this definitely seems more like what I'm after, considering they have an `HTTP` action which lets you define the retry policy on `408`, `429` and `5XX` return codes. Now I need to figure out how to extract parts of the brokered message for the authorization header and the message body – Andrew Williamson Apr 18 '18 at 19:49
1

Although still in preview (not recommended for production code), you could use Durable Functions. If you want to maintain the ability to manipulate objects in code, this is likely your best bet!

(+1 to LogicApp solution too!)

Marie Hoeger
  • 1,261
  • 9
  • 11