Skip to content
This repository was archived by the owner on Oct 12, 2023. It is now read-only.

Commit 78c960d

Browse files
Merge pull request #201 from gavinfish/sender-retry
Only retry with retryable amqp errors for sender
2 parents 705d239 + 9d82129 commit 78c960d

File tree

3 files changed

+86
-25
lines changed

3 files changed

+86
-25
lines changed

changelog.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
# Change Log
22

3+
## `v0.10.8`
4+
- only retry with retryable amqp errors for sender [#201](https://github.com/Azure/azure-service-bus-go/issues/201)
5+
6+
## `v0.10.7`
7+
- add AzureEnvironment namespace option and use its definition [#192](https://github.com/Azure/azure-service-bus-go/issues/192)
8+
- fix for Websocket behind Proxy Issue [#196](https://github.com/Azure/azure-service-bus-go/issues/196)
9+
- fix nil error dereference [#199](https://github.com/Azure/azure-service-bus-go/issues/199)
10+
11+
## `v0.10.6`
12+
- fix a hang when closing a receiver
13+
14+
## `v0.10.5`
15+
- recover must rebuild the link atomically [#187](https://github.com/Azure/azure-service-bus-go/issues/187)
16+
17+
## `v0.10.4`
18+
- updates dependencies to their latest versions
19+
320
## `v0.10.3`
421
- Implements DefaultRuleDescription to allow setting a default rule for a subscription.
522

errors.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,25 @@ package servicebus
33
import (
44
"fmt"
55
"reflect"
6+
"time"
67

78
"github.com/Azure/azure-amqp-common-go/v3/rpc"
9+
"github.com/Azure/go-amqp"
10+
)
11+
12+
// Error Conditions
13+
const (
14+
// Service Bus Errors
15+
errorServerBusy amqp.ErrorCondition = "com.microsoft:server-busy"
16+
errorTimeout amqp.ErrorCondition = "com.microsoft:timeout"
17+
errorOperationCancelled amqp.ErrorCondition = "com.microsoft:operation-cancelled"
18+
errorContainerClose amqp.ErrorCondition = "com.microsoft:container-close"
19+
)
20+
21+
const (
22+
amqpRetryDefaultTimes int = 3
23+
amqpRetryDefaultDelay time.Duration = time.Second
24+
amqpRetryBusyServerDelay time.Duration = 10 * time.Second
825
)
926

1027
type (

sender.go

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ package servicebus
2424

2525
import (
2626
"context"
27+
"errors"
2728
"sync"
2829
"time"
2930

@@ -234,31 +235,10 @@ func (s *Sender) trySend(ctx context.Context, evt eventer) error {
234235

235236
switch err.(type) {
236237
case *amqp.Error, *amqp.DetachError:
237-
tab.For(ctx).Debug("recovering connection")
238-
_, retryErr := common.Retry(10, 10*time.Second, func() (interface{}, error) {
239-
ctx, sp := s.startProducerSpanFromContext(ctx, "sb.Sender.trySend.tryRecover")
240-
defer sp.End()
241-
242-
err := s.Recover(ctx)
243-
if err == nil {
244-
tab.For(ctx).Debug("recovered connection")
245-
return nil, nil
246-
}
247-
248-
select {
249-
case <-ctx.Done():
250-
return nil, ctx.Err()
251-
default:
252-
return nil, common.Retryable(err.Error())
253-
}
254-
})
255-
256-
if retryErr != nil {
257-
tab.For(ctx).Debug("sender recovering retried, but error was unrecoverable")
258-
if err := s.Close(ctx); err != nil {
259-
tab.For(ctx).Error(err)
260-
}
261-
return retryErr
238+
err = s.handleAMQPError(ctx, err)
239+
if err != nil {
240+
tab.For(ctx).Error(err)
241+
return err
262242
}
263243
default:
264244
tab.For(ctx).Error(err)
@@ -268,6 +248,53 @@ func (s *Sender) trySend(ctx context.Context, evt eventer) error {
268248
}
269249
}
270250

251+
// handleAMQPError is called internally when an event has failed to send so we
252+
// can parse the error to determine whether we should attempt to retry sending the event again.
253+
func (s *Sender) handleAMQPError(ctx context.Context, err error) error {
254+
var amqpError *amqp.Error
255+
if errors.As(err, &amqpError) {
256+
switch amqpError.Condition {
257+
case errorServerBusy:
258+
return s.retryRetryableAmqpError(ctx, amqpRetryDefaultTimes, amqpRetryBusyServerDelay)
259+
case errorTimeout:
260+
return s.retryRetryableAmqpError(ctx, amqpRetryDefaultTimes, amqpRetryDefaultDelay)
261+
case errorOperationCancelled:
262+
return s.retryRetryableAmqpError(ctx, amqpRetryDefaultTimes, amqpRetryDefaultDelay)
263+
case errorContainerClose:
264+
return s.retryRetryableAmqpError(ctx, amqpRetryDefaultTimes, amqpRetryDefaultDelay)
265+
default:
266+
return err
267+
}
268+
}
269+
return s.retryRetryableAmqpError(ctx, amqpRetryDefaultTimes, amqpRetryDefaultDelay)
270+
}
271+
272+
func (s *Sender) retryRetryableAmqpError(ctx context.Context, times int, delay time.Duration) error {
273+
tab.For(ctx).Debug("recovering sender connection")
274+
_, retryErr := common.Retry(times, delay, func() (interface{}, error) {
275+
ctx, sp := s.startProducerSpanFromContext(ctx, "sb.Sender.trySend.tryRecover")
276+
defer sp.End()
277+
278+
err := s.Recover(ctx)
279+
if err == nil {
280+
tab.For(ctx).Debug("recovered connection")
281+
return nil, nil
282+
}
283+
284+
select {
285+
case <-ctx.Done():
286+
return nil, ctx.Err()
287+
default:
288+
return nil, common.Retryable(err.Error())
289+
}
290+
})
291+
if retryErr != nil {
292+
tab.For(ctx).Debug("sender recovering retried, but error was unrecoverable")
293+
return retryErr
294+
}
295+
return nil
296+
}
297+
271298
func (s *Sender) connClosedError(ctx context.Context) error {
272299
name := "Sender"
273300
if s.Name != "" {

0 commit comments

Comments
 (0)