@@ -19,13 +19,12 @@ import (
1919 "errors"
2020 "strings"
2121 "sync"
22- "sync/atomic"
2322 "time"
2423
2524 "github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
2625 servicebus "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus"
2726 sbadmin "github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus/admin"
28- "github.com/cenkalti/backoff/v4 "
27+ "go.uber.org/ratelimit "
2928
3029 azauth "github.com/dapr/components-contrib/authentication/azure"
3130 "github.com/dapr/components-contrib/bindings"
@@ -39,33 +38,37 @@ const (
3938 label = "label"
4039 id = "id"
4140
42- // AzureServiceBusDefaultMessageTimeToLive defines the default time to live for queues, which is 14 days. The same way Azure Portal does.
43- AzureServiceBusDefaultMessageTimeToLive = time .Hour * 24 * 14
41+ // azureServiceBusDefaultMessageTimeToLive defines the default time to live for queues, which is 14 days. The same way Azure Portal does.
42+ azureServiceBusDefaultMessageTimeToLive = time .Hour * 24 * 14
4443
4544 // Default timeout in seconds
46- DefaultTimeoutInSec = 60
45+ defaultTimeoutInSec = 60
46+
47+ // Default rate of retriable errors per second
48+ defaultMaxRetriableErrorsPerSec = 10
4749)
4850
4951// AzureServiceBusQueues is an input/output binding reading from and sending events to Azure Service Bus queues.
5052type AzureServiceBusQueues struct {
51- metadata * serviceBusQueuesMetadata
52- client * servicebus.Client
53- adminClient * sbadmin.Client
54- shutdownSignal int32
55- timeout time. Duration
56- sender * servicebus. Sender
57- senderLock sync. RWMutex
58- logger logger.Logger
59- ctx context.Context
60- cancel context.CancelFunc
53+ metadata * serviceBusQueuesMetadata
54+ client * servicebus.Client
55+ adminClient * sbadmin.Client
56+ timeout time. Duration
57+ sender * servicebus. Sender
58+ senderLock sync. RWMutex
59+ retriableErrLimit ratelimit. Limiter
60+ logger logger.Logger
61+ ctx context.Context
62+ cancel context.CancelFunc
6163}
6264
6365type serviceBusQueuesMetadata struct {
64- ConnectionString string `json:"connectionString"`
65- NamespaceName string `json:"namespaceName,omitempty"`
66- QueueName string `json:"queueName"`
67- TimeoutInSec int `json:"timeoutInSec"`
68- ttl time.Duration
66+ ConnectionString string `json:"connectionString"`
67+ NamespaceName string `json:"namespaceName,omitempty"`
68+ QueueName string `json:"queueName"`
69+ TimeoutInSec int `json:"timeoutInSec"`
70+ MaxRetriableErrorsPerSec * int `json:"maxRetriableErrorsPerSec"`
71+ ttl time.Duration
6972}
7073
7174// NewAzureServiceBusQueues returns a new AzureServiceBusQueues instance.
@@ -83,6 +86,11 @@ func (a *AzureServiceBusQueues) Init(metadata bindings.Metadata) (err error) {
8386 return err
8487 }
8588 a .timeout = time .Duration (a .metadata .TimeoutInSec ) * time .Second
89+ if * a .metadata .MaxRetriableErrorsPerSec > 0 {
90+ a .retriableErrLimit = ratelimit .New (* a .metadata .MaxRetriableErrorsPerSec )
91+ } else {
92+ a .retriableErrLimit = ratelimit .NewUnlimited ()
93+ }
8694
8795 userAgent := "dapr-" + logger .DaprVersion
8896 if a .metadata .ConnectionString != "" {
@@ -144,8 +152,6 @@ func (a *AzureServiceBusQueues) Init(metadata bindings.Metadata) (err error) {
144152 }
145153 }
146154
147- a .clearShutdown ()
148-
149155 a .ctx , a .cancel = context .WithCancel (context .Background ())
150156
151157 return nil
@@ -173,15 +179,22 @@ func (a *AzureServiceBusQueues) parseMetadata(metadata bindings.Metadata) (*serv
173179 }
174180 if ! ok {
175181 // set the same default message time to live as suggested in Azure Portal to 14 days (otherwise it will be 10675199 days)
176- ttl = AzureServiceBusDefaultMessageTimeToLive
182+ ttl = azureServiceBusDefaultMessageTimeToLive
177183 }
178184 m .ttl = ttl
179185
180186 // Queue names are case-insensitive and are forced to lowercase. This mimics the Azure portal's behavior.
181187 m .QueueName = strings .ToLower (m .QueueName )
182188
183189 if m .TimeoutInSec < 1 {
184- m .TimeoutInSec = DefaultTimeoutInSec
190+ m .TimeoutInSec = defaultTimeoutInSec
191+ }
192+
193+ if m .MaxRetriableErrorsPerSec == nil {
194+ m .MaxRetriableErrorsPerSec = to .Ptr (defaultMaxRetriableErrorsPerSec )
195+ }
196+ if * m .MaxRetriableErrorsPerSec < 0 {
197+ return nil , errors .New ("maxRetriableErrorsPerSec must be non-negative" )
185198 }
186199
187200 return & m , nil
@@ -232,26 +245,34 @@ func (a *AzureServiceBusQueues) Invoke(ctx context.Context, req *bindings.Invoke
232245}
233246
234247func (a * AzureServiceBusQueues ) Read (handler func (context.Context , * bindings.ReadResponse ) ([]byte , error )) error {
235- // Connections need to retry forever with a maximum backoff of 5 minutes and exponential scaling.
236- connConfig := retry .DefaultConfig ()
237- connConfig .Policy = retry .PolicyExponential
238- connConfig .MaxInterval = 5 * time .Minute
239- connBackoff := connConfig .NewBackOffWithContext (a .ctx )
240-
241- for ! a .isShutdown () {
242- receiver := a .attemptConnectionForever (connBackoff )
248+ for a .ctx .Err () == nil {
249+ receiver := a .attemptConnectionForever ()
243250 if receiver == nil {
244251 a .logger .Errorf ("Failed to connect to Azure Service Bus Queue." )
245252 continue
246253 }
247254
248- msgs , err := receiver .ReceiveMessages (a .ctx , 10 , nil )
249- if err != nil {
250- a .logger .Warnf ("Error reading from Azure Service Bus Queue binding: %s" , err .Error ())
251- }
255+ // Receive messages loop
256+ // This continues until the context is canceled
257+ for a .ctx .Err () == nil {
258+ // Blocks until the connection is closed or the context is canceled
259+ msgs , err := receiver .ReceiveMessages (a .ctx , 1 , nil )
260+ if err != nil {
261+ a .logger .Warnf ("Error reading from Azure Service Bus Queue binding: %s" , err .Error ())
262+ }
263+
264+ l := len (msgs )
265+ if l == 0 {
266+ // We got no message, which is unusual too
267+ a .logger .Warn ("Received 0 messages from Service Bus" )
268+ continue
269+ } else if l > 1 {
270+ // We are requesting one message only; this should never happen
271+ a .logger .Errorf ("Expected one message from Service Bus, but received %d" , l )
272+ }
273+
274+ msg := msgs [0 ]
252275
253- // Blocks until the connection is closed
254- for _ , msg := range msgs {
255276 body , err := msg .Body ()
256277 if err != nil {
257278 a .logger .Warnf ("Error reading message body: %s" , err .Error ())
@@ -277,7 +298,10 @@ func (a *AzureServiceBusQueues) Read(handler func(context.Context, *bindings.Rea
277298 continue
278299 }
279300
280- err = receiver .CompleteMessage (a .ctx , msg , nil )
301+ // Use a background context in case a.ctx has been canceled already
302+ ctx , cancel := context .WithTimeout (context .Background (), a .timeout )
303+ err = receiver .CompleteMessage (ctx , msg , nil )
304+ cancel ()
281305 if err != nil {
282306 a .logger .Warnf ("Error completing message: %s" , err .Error ())
283307 continue
@@ -297,17 +321,30 @@ func (a *AzureServiceBusQueues) Read(handler func(context.Context, *bindings.Rea
297321}
298322
299323func (a * AzureServiceBusQueues ) abandonMessage (receiver * servicebus.Receiver , msg * servicebus.ReceivedMessage ) {
300- ctx , cancel := context .WithTimeout (a .ctx , a .timeout )
324+ // Use a background context in case a.ctx has been canceled already
325+ ctx , cancel := context .WithTimeout (context .Background (), a .timeout )
301326 err := receiver .AbandonMessage (ctx , msg , nil )
327+ cancel ()
302328 if err != nil {
303329 // Log only
304330 a .logger .Warnf ("Error abandoning message: %s" , err .Error ())
305331 }
306- cancel ()
332+
333+ // If we're here, it means we got a retriable error, so we need to consume a retriable error token before this (synchronous) method returns
334+ // If there have been too many retriable errors per second, this method slows the consuming down
335+ a .logger .Debugf ("Taking a retriable error token" )
336+ before := time .Now ()
337+ _ = a .retriableErrLimit .Take ()
338+ a .logger .Debugf ("Resumed after pausing for %v" , time .Now ().Sub (before ))
307339}
308340
309- func (a * AzureServiceBusQueues ) attemptConnectionForever (backoff backoff.BackOff ) * servicebus.Receiver {
310- var receiver * servicebus.Receiver
341+ func (a * AzureServiceBusQueues ) attemptConnectionForever () (receiver * servicebus.Receiver ) {
342+ // Connections need to retry forever with a maximum backoff of 5 minutes and exponential scaling.
343+ config := retry .DefaultConfig ()
344+ config .Policy = retry .PolicyExponential
345+ config .MaxInterval = 5 * time .Minute
346+ backoff := config .NewBackOffWithContext (a .ctx )
347+
311348 retry .NotifyRecover (
312349 func () error {
313350 clientAttempt , err := a .client .NewReceiverForQueue (a .metadata .QueueName , nil )
@@ -345,19 +382,5 @@ func (a *AzureServiceBusQueues) Close() (err error) {
345382 return err
346383 }
347384 }
348- a .setShutdown ()
349385 return nil
350386}
351-
352- func (a * AzureServiceBusQueues ) setShutdown () {
353- atomic .CompareAndSwapInt32 (& a .shutdownSignal , 0 , 1 )
354- }
355-
356- func (a * AzureServiceBusQueues ) clearShutdown () {
357- atomic .CompareAndSwapInt32 (& a .shutdownSignal , 1 , 0 )
358- }
359-
360- func (a * AzureServiceBusQueues ) isShutdown () bool {
361- val := atomic .LoadInt32 (& a .shutdownSignal )
362- return val == 1
363- }
0 commit comments