github.com/wfusion/gofusion@v1.1.14/common/infra/watermill/pkg/publisher/retry.go (about) 1 package publisher 2 3 import ( 4 "context" 5 "time" 6 7 "github.com/pkg/errors" 8 9 "github.com/wfusion/gofusion/common/infra/watermill" 10 "github.com/wfusion/gofusion/common/infra/watermill/message" 11 ) 12 13 var ( 14 ErrNonPositiveNumberOfRetries = errors.New("number of retries should be positive") 15 ErrNonPositiveTimeToFirstRetry = errors.New("time to first retry should be positive") 16 ) 17 18 type RetryPublisherConfig struct { 19 MaxRetries int 20 // each subsequent retry doubles the time to next retry. 21 TimeToFirstRetry time.Duration 22 Logger watermill.LoggerAdapter 23 } 24 25 func (c *RetryPublisherConfig) setDefaults() { 26 if c.MaxRetries == 0 { 27 c.MaxRetries = 5 28 } 29 30 if c.TimeToFirstRetry == 0 { 31 c.TimeToFirstRetry = time.Second 32 } 33 34 if c.Logger == nil { 35 c.Logger = watermill.NopLogger{} 36 } 37 } 38 39 func (c RetryPublisherConfig) validate() error { 40 if c.MaxRetries <= 0 { 41 return ErrNonPositiveNumberOfRetries 42 } 43 if c.TimeToFirstRetry <= 0 { 44 return ErrNonPositiveTimeToFirstRetry 45 } 46 47 return nil 48 } 49 50 // RetryPublisher is a decorator for a publisher that retries message publishing after a failure. 51 type RetryPublisher struct { 52 pub message.Publisher 53 config RetryPublisherConfig 54 } 55 56 func NewRetryPublisher(pub message.Publisher, config RetryPublisherConfig) (*RetryPublisher, error) { 57 config.setDefaults() 58 59 if err := config.validate(); err != nil { 60 return nil, errors.Wrap(err, "invalid RetryPublisher config") 61 } 62 63 return &RetryPublisher{ 64 pub, 65 config, 66 }, nil 67 } 68 69 func (p RetryPublisher) Publish(ctx context.Context, topic string, messages ...*message.Message) error { 70 failedMessages := NewErrCouldNotPublish() 71 72 // todo: do some parallel processing maybe? this is a very basic implementation 73 for _, msg := range messages { 74 err := p.send(ctx, topic, msg) 75 if err != nil { 76 failedMessages.addMsg(msg, err) 77 } 78 } 79 80 if failedMessages.Len() > 0 { 81 return failedMessages 82 } 83 84 return nil 85 } 86 87 func (p RetryPublisher) Close() error { 88 return p.pub.Close() 89 } 90 91 // send sends one message at a time to prevent sending a successful message more than once. 92 func (p RetryPublisher) send(ctx context.Context, topic string, msg *message.Message) error { 93 var err error 94 timeToNextRetry := p.config.TimeToFirstRetry 95 96 for i := 0; i < p.config.MaxRetries; i++ { 97 err = p.pub.Publish(ctx, topic, msg) 98 if err == nil { 99 return nil 100 } 101 102 p.config.Logger.Info("Publish failed, retrying in "+timeToNextRetry.String(), watermill.LogFields{ 103 "error": err, 104 }) 105 time.Sleep(timeToNextRetry) 106 timeToNextRetry *= 2 107 } 108 return err 109 }