Skip to content

Commit e35dff8

Browse files
committed
enhance retry mechanism with logging and context support
1 parent 1da3cda commit e35dff8

6 files changed

Lines changed: 120 additions & 38 deletions

File tree

pkg/db/postgres/logger.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package postgres
2+
3+
type logger interface {
4+
Infof(format string, args ...any)
5+
}

pkg/db/postgres/postgres.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,18 @@ func New(ctx context.Context, cfg Config) (*PostgreSQL, error) {
4444
// CheckConnection checks if the PostgreSQL connection pool is initialized and attempts to ping the database.
4545
// It retries the ping operation up to 5 times with a 2-second delay between attempts.
4646
// If the connection pool is not initialized, it returns an error.
47-
func (p *PostgreSQL) CheckConnection(ctx context.Context) error {
47+
func (p *PostgreSQL) CheckConnection(ctx context.Context, logger logger) error {
4848
if p.DB == nil {
4949
return fmt.Errorf("PostgreSQL connection pool is not initialized")
5050
}
5151

52-
return retry.New(retry.WithDelay(time.Second*2), retry.WithMaxAttempts(5)).Do(func() error {
52+
return retry.New(
53+
retry.WithDelay(time.Second*2),
54+
retry.WithMaxAttempts(5),
55+
retry.WithPolicy(retry.PolicyLinear),
56+
retry.WithLogger(logger),
57+
retry.WithContext(ctx),
58+
).Do(func() error {
5359
return p.DB.Ping(ctx)
5460
})
5561
}

pkg/retry/logger.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package retry
2+
3+
type logger interface {
4+
Infof(format string, args ...any)
5+
}

pkg/retry/options.go

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ import (
77

88
type Option func(*Retry)
99

10+
// WithLogger sets the logger for the Retry instance
11+
func WithLogger(logger logger) Option {
12+
return func(r *Retry) {
13+
r.logger = logger
14+
}
15+
}
16+
1017
// WithMaxAttempts sets the maximum number of attempts
1118
func WithMaxAttempts(maxAttempts int) Option {
1219
return func(r *Retry) {
@@ -28,20 +35,33 @@ func WithDelay(delay time.Duration) Option {
2835
}
2936
}
3037

31-
// WithDebug sets the debug mode
32-
func WithDebug(debug bool) Option {
38+
// WithContext sets the ctx for Infinite policy retry
39+
func WithContext(ctx context.Context) Option {
3340
return func(r *Retry) {
34-
r.debug = debug
41+
r.ctx = ctx
3542
}
3643
}
3744

38-
// WithContext sets the ctx for Infinite policy retry
39-
func WithContext(ctx context.Context) Option {
45+
// WithOnFailedFn sets the function to be called on failure
46+
func WithOnFailedFn(fn func()) Option {
4047
return func(r *Retry) {
41-
r.ctx = ctx
48+
r.onFailedFn = fn
4249
}
4350
}
4451

52+
// WithOnSuccessFn sets the function to be called on success
53+
func WithOnSuccessFn(fn func()) Option {
54+
return func(r *Retry) {
55+
r.onSuccessFn = fn
56+
}
57+
}
58+
59+
// SetLogger sets the logger for the Retry instance
60+
func (r *Retry) SetLogger(logger logger) *Retry {
61+
r.logger = logger
62+
return r
63+
}
64+
4565
// SetMaxAttempts sets the maximum number of attempts
4666
func (r *Retry) SetMaxAttempts(maxAttempts int) *Retry {
4767
r.maxAttempts = maxAttempts
@@ -60,8 +80,20 @@ func (r *Retry) SetDelay(delay time.Duration) *Retry {
6080
return r
6181
}
6282

63-
// SetDebug sets the debug mode
64-
func (r *Retry) SetDebug(debug bool) *Retry {
65-
r.debug = debug
83+
// SetContext sets the ctx for Infinite policy retry
84+
func (r *Retry) SetContext(ctx context.Context) *Retry {
85+
r.ctx = ctx
86+
return r
87+
}
88+
89+
// SetOnFailedFn sets the function to be called on failure
90+
func (r *Retry) SetOnFailedFn(fn func()) *Retry {
91+
r.onFailedFn = fn
92+
return r
93+
}
94+
95+
// SetOnSuccessFn sets the function to be called on success
96+
func (r *Retry) SetOnSuccessFn(fn func()) *Retry {
97+
r.onSuccessFn = fn
6698
return r
6799
}

pkg/retry/retry.go

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ import (
1010
// Retry represents a retry mechanism
1111
type Retry struct {
1212
ctx context.Context
13+
logger logger
1314
maxAttempts int
1415
policy Policy
1516
delay time.Duration
16-
debug bool
17+
onFailedFn func()
18+
onSuccessFn func()
1719
}
1820

1921
// New creates a new Retry instance
@@ -37,16 +39,27 @@ func New(opts ...Option) *Retry {
3739

3840
// Do - performs the retry mechanism
3941
func (r *Retry) Do(fn func() error) error {
42+
if fn == nil {
43+
return fmt.Errorf("retry function cannot be nil")
44+
}
45+
46+
var err error
4047
switch r.policy {
4148
case PolicyLinear:
42-
return r.linearRetry(fn)
49+
err = r.linearRetry(fn)
4350
case PolicyBackoff:
44-
return r.backoffRetry(fn)
51+
err = r.backoffRetry(fn)
4552
case PolicyInfinite:
46-
return r.infiniteRetry(fn)
53+
err = r.infiniteRetry(fn)
4754
default:
48-
return fmt.Errorf("unsupported retry policy")
55+
err = fmt.Errorf("unsupported retry policy")
56+
}
57+
58+
if err == nil && r.onSuccessFn != nil {
59+
r.onSuccessFn()
4960
}
61+
62+
return err
5063
}
5164

5265
// linearRetry - performs a linear retry mechanism
@@ -57,13 +70,17 @@ func (r *Retry) linearRetry(fn func() error) error {
5770
return nil
5871
}
5972

73+
if r.onFailedFn != nil {
74+
r.onFailedFn()
75+
}
76+
6077
if errors.Is(err, ErrExit) {
6178
return err
6279
}
6380

6481
if attempt < r.maxAttempts {
65-
if r.debug {
66-
fmt.Printf("linear Retry attempt %d failed, retrying in %s...\n", attempt, r.delay)
82+
if r.logger != nil {
83+
r.logger.Infof("linear retry attempt %d failed, retrying in %s...", attempt, r.delay)
6784
}
6885
time.Sleep(r.delay)
6986
}
@@ -79,14 +96,18 @@ func (r *Retry) backoffRetry(fn func() error) error {
7996
return nil
8097
}
8198

99+
if r.onFailedFn != nil {
100+
r.onFailedFn()
101+
}
102+
82103
if errors.Is(err, ErrExit) {
83104
return err
84105
}
85106

86107
if attempt < r.maxAttempts {
87108
delay := r.delay * (1 << (attempt - 1)) // Увеличение задержки в 2 раза на каждую попытку
88-
if r.debug {
89-
fmt.Printf("backoff Retry attempt %d failed, retrying in %s...\n", attempt, delay)
109+
if r.logger != nil {
110+
r.logger.Infof("backoff retry attempt %d failed, retrying in %s...", attempt, delay)
90111
}
91112
time.Sleep(delay)
92113
}
@@ -112,13 +133,17 @@ func (r *Retry) infiniteRetry(fn func() error) error {
112133
return
113134
}
114135

136+
if r.onFailedFn != nil {
137+
r.onFailedFn()
138+
}
139+
115140
if errors.Is(err, ErrExit) {
116141
resCh <- err
117142
return
118143
}
119144

120-
if r.debug {
121-
fmt.Printf("initnite retry attempt\n")
145+
if r.logger != nil {
146+
r.logger.Infof("infinite retry attempt failed, retrying in %s...", r.delay)
122147
}
123148
time.Sleep(r.delay)
124149
}

pkg/retry/retry_test.go

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,64 @@
11
package retry_test
22

33
import (
4+
"fmt"
45
"testing"
56
"time"
67

78
"github.com/stretchr/testify/require"
89
"github.com/tkcrm/modules/pkg/retry"
910
)
1011

12+
type logger struct{}
13+
14+
func (l *logger) Infof(format string, args ...any) {
15+
fmt.Println(fmt.Sprintf(format, args...))
16+
}
17+
1118
func TestRetry_Do(t *testing.T) {
19+
l := &logger{}
20+
1221
t.Run("linear retry", func(t *testing.T) {
13-
r := retry.New(retry.WithPolicy(retry.PolicyLinear), retry.WithMaxAttempts(3), retry.WithDelay(200*time.Millisecond)).SetDebug(true)
22+
r := retry.New(retry.WithPolicy(retry.PolicyLinear), retry.WithMaxAttempts(3), retry.WithDelay(200*time.Millisecond)).SetLogger(l)
1423
err := r.Do(func() error {
1524
return nil
1625
})
1726
require.NoError(t, err)
1827
})
1928
t.Run("backoff retry", func(t *testing.T) {
20-
r := retry.New(retry.WithPolicy(retry.PolicyBackoff), retry.WithMaxAttempts(3), retry.WithDelay(200*time.Millisecond)).SetDebug(true)
29+
r := retry.New(retry.WithPolicy(retry.PolicyBackoff), retry.WithMaxAttempts(3), retry.WithDelay(200*time.Millisecond)).SetLogger(l)
2130
err := r.Do(func() error {
2231
return nil
2332
})
2433
require.NoError(t, err)
2534
})
2635

2736
t.Run("unsupported retry policy", func(t *testing.T) {
28-
r := retry.New(retry.WithPolicy(retry.Policy(3)), retry.WithMaxAttempts(3), retry.WithDelay(200*time.Millisecond)).SetDebug(true)
37+
r := retry.New(retry.WithPolicy(retry.Policy(3)), retry.WithMaxAttempts(3), retry.WithDelay(200*time.Millisecond)).SetLogger(l)
2938
err := r.Do(func() error {
3039
return nil
3140
})
3241
require.Error(t, err)
3342
})
3443

3544
t.Run("linear retry failed", func(t *testing.T) {
36-
r := retry.New(retry.WithPolicy(retry.PolicyLinear), retry.WithMaxAttempts(3), retry.WithDelay(200*time.Millisecond)).SetDebug(true)
45+
r := retry.New(retry.WithPolicy(retry.PolicyLinear), retry.WithMaxAttempts(3), retry.WithDelay(200*time.Millisecond)).SetLogger(l)
3746
err := r.Do(func() error {
3847
return retry.ErrRetry
3948
})
4049
require.Error(t, err)
4150
})
4251

4352
t.Run("backoff retry failed", func(t *testing.T) {
44-
r := retry.New(retry.WithPolicy(retry.PolicyBackoff), retry.WithMaxAttempts(3), retry.WithDelay(200*time.Millisecond)).SetDebug(true)
53+
r := retry.New(retry.WithPolicy(retry.PolicyBackoff), retry.WithMaxAttempts(3), retry.WithDelay(200*time.Millisecond)).SetLogger(l)
4554
err := r.Do(func() error {
4655
return retry.ErrRetry
4756
})
4857
require.Error(t, err)
4958
})
5059

5160
t.Run("linear retry failed after 3 attempts", func(t *testing.T) {
52-
r := retry.New(retry.WithPolicy(retry.PolicyLinear), retry.WithMaxAttempts(3), retry.WithDelay(200*time.Millisecond)).SetDebug(true)
61+
r := retry.New(retry.WithPolicy(retry.PolicyLinear), retry.WithMaxAttempts(3), retry.WithDelay(200*time.Millisecond)).SetLogger(l)
5362
err := r.Do(func() error {
5463
return retry.ErrRetry
5564
})
@@ -58,7 +67,7 @@ func TestRetry_Do(t *testing.T) {
5867
})
5968

6069
t.Run("backoff retry failed after 3 attempts", func(t *testing.T) {
61-
r := retry.New(retry.WithPolicy(retry.PolicyBackoff), retry.WithMaxAttempts(3), retry.WithDelay(200*time.Millisecond)).SetDebug(true)
70+
r := retry.New(retry.WithPolicy(retry.PolicyBackoff), retry.WithMaxAttempts(3), retry.WithDelay(200*time.Millisecond)).SetLogger(l)
6271
err := r.Do(func() error {
6372
return retry.ErrRetry
6473
})
@@ -67,7 +76,7 @@ func TestRetry_Do(t *testing.T) {
6776
})
6877

6978
t.Run("linear retry failed after 3 attempts with custom delay", func(t *testing.T) {
70-
r := retry.New(retry.WithPolicy(retry.PolicyLinear), retry.WithMaxAttempts(3), retry.WithDelay(400*time.Millisecond)).SetDebug(true)
79+
r := retry.New(retry.WithPolicy(retry.PolicyLinear), retry.WithMaxAttempts(3), retry.WithDelay(400*time.Millisecond)).SetLogger(l)
7180
err := r.Do(func() error {
7281
return retry.ErrRetry
7382
})
@@ -76,7 +85,7 @@ func TestRetry_Do(t *testing.T) {
7685
})
7786

7887
t.Run("backoff retry failed after 3 attempts with custom delay", func(t *testing.T) {
79-
r := retry.New(retry.WithPolicy(retry.PolicyBackoff), retry.WithMaxAttempts(3), retry.WithDelay(400*time.Millisecond)).SetDebug(true)
88+
r := retry.New(retry.WithPolicy(retry.PolicyBackoff), retry.WithMaxAttempts(3), retry.WithDelay(400*time.Millisecond)).SetLogger(l)
8089
err := r.Do(func() error {
8190
return retry.ErrRetry
8291
})
@@ -85,7 +94,7 @@ func TestRetry_Do(t *testing.T) {
8594
})
8695

8796
t.Run("linear retry failed after 3 attempts with custom max attempts", func(t *testing.T) {
88-
r := retry.New(retry.WithPolicy(retry.PolicyLinear), retry.WithMaxAttempts(5), retry.WithDelay(200*time.Millisecond)).SetDebug(true)
97+
r := retry.New(retry.WithPolicy(retry.PolicyLinear), retry.WithMaxAttempts(5), retry.WithDelay(200*time.Millisecond)).SetLogger(l)
8998
err := r.Do(func() error {
9099
return retry.ErrRetry
91100
})
@@ -94,7 +103,7 @@ func TestRetry_Do(t *testing.T) {
94103
})
95104

96105
t.Run("backoff retry failed after 3 attempts with custom max attempts", func(t *testing.T) {
97-
r := retry.New(retry.WithPolicy(retry.PolicyBackoff), retry.WithMaxAttempts(5), retry.WithDelay(200*time.Millisecond)).SetDebug(true)
106+
r := retry.New(retry.WithPolicy(retry.PolicyBackoff), retry.WithMaxAttempts(5), retry.WithDelay(200*time.Millisecond)).SetLogger(l)
98107
err := r.Do(func() error {
99108
return retry.ErrRetry
100109
})
@@ -103,7 +112,7 @@ func TestRetry_Do(t *testing.T) {
103112
})
104113

105114
t.Run("linear retry failed after 3 attempts with custom max attempts and delay", func(t *testing.T) {
106-
r := retry.New(retry.WithPolicy(retry.PolicyLinear), retry.WithMaxAttempts(5), retry.WithDelay(400*time.Millisecond)).SetDebug(true)
115+
r := retry.New(retry.WithPolicy(retry.PolicyLinear), retry.WithMaxAttempts(5), retry.WithDelay(400*time.Millisecond)).SetLogger(l)
107116
err := r.Do(func() error {
108117
return retry.ErrRetry
109118
})
@@ -112,7 +121,7 @@ func TestRetry_Do(t *testing.T) {
112121
})
113122

114123
t.Run("backoff retry failed after 3 attempts with custom max attempts and delay", func(t *testing.T) {
115-
r := retry.New(retry.WithPolicy(retry.PolicyBackoff), retry.WithMaxAttempts(5), retry.WithDelay(400*time.Millisecond)).SetDebug(true)
124+
r := retry.New(retry.WithPolicy(retry.PolicyBackoff), retry.WithMaxAttempts(5), retry.WithDelay(400*time.Millisecond)).SetLogger(l)
116125
err := r.Do(func() error {
117126
return retry.ErrRetry
118127
})
@@ -121,7 +130,7 @@ func TestRetry_Do(t *testing.T) {
121130
})
122131

123132
t.Run("linear retry failed after 3 attempts with custom max attempts and delay and custom retry function", func(t *testing.T) {
124-
r := retry.New(retry.WithPolicy(retry.PolicyLinear), retry.WithMaxAttempts(5), retry.WithDelay(400*time.Millisecond)).SetDebug(true)
133+
r := retry.New(retry.WithPolicy(retry.PolicyLinear), retry.WithMaxAttempts(5), retry.WithDelay(400*time.Millisecond)).SetLogger(l)
125134
err := r.Do(func() error {
126135
return retry.ErrRetry
127136
})
@@ -130,7 +139,7 @@ func TestRetry_Do(t *testing.T) {
130139
})
131140

132141
t.Run("backoff retry failed after 3 attempts with custom max attempts and delay and custom retry function", func(t *testing.T) {
133-
r := retry.New(retry.WithPolicy(retry.PolicyBackoff), retry.WithMaxAttempts(5), retry.WithDelay(400*time.Millisecond)).SetDebug(true)
142+
r := retry.New(retry.WithPolicy(retry.PolicyBackoff), retry.WithMaxAttempts(5), retry.WithDelay(400*time.Millisecond)).SetLogger(l)
134143
err := r.Do(func() error {
135144
return retry.ErrRetry
136145
})
@@ -139,7 +148,7 @@ func TestRetry_Do(t *testing.T) {
139148
})
140149

141150
t.Run("linear retry failed with exit error", func(t *testing.T) {
142-
r := retry.New(retry.WithPolicy(retry.PolicyLinear), retry.WithDelay(200*time.Millisecond)).SetDebug(true)
151+
r := retry.New(retry.WithPolicy(retry.PolicyLinear), retry.WithDelay(200*time.Millisecond)).SetLogger(l)
143152
var attempt int
144153
err := r.Do(func() error {
145154
attempt++

0 commit comments

Comments
 (0)