Skip to content

Commit f0d1177

Browse files
authored
Merge pull request #6 from codercom/refactor-proposal
More flexible refactor.
2 parents dab75df + 47ac9d9 commit f0d1177

File tree

4 files changed

+392
-440
lines changed

4 files changed

+392
-440
lines changed

listener.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package retry
2+
3+
import (
4+
"log"
5+
"net"
6+
"time"
7+
)
8+
9+
type Listener struct {
10+
LogTmpErr func(err error)
11+
net.Listener
12+
}
13+
14+
const (
15+
initialListenerDelay = 5 * time.Millisecond
16+
maxListenerDelay = time.Second
17+
)
18+
19+
func (l *Listener) Accept() (net.Conn, error) {
20+
var retryDelay time.Duration
21+
for {
22+
c, err := l.Listener.Accept()
23+
if err != nil {
24+
ne, ok := err.(net.Error)
25+
if ok && ne.Temporary() {
26+
if retryDelay == 0 {
27+
retryDelay = initialListenerDelay
28+
} else {
29+
retryDelay *= 2
30+
if retryDelay > maxListenerDelay {
31+
retryDelay = maxListenerDelay
32+
}
33+
}
34+
if l.LogTmpErr == nil {
35+
log.Printf("retry: temp error accepting next connection: %v; retrying in %v", err, retryDelay)
36+
} else {
37+
l.LogTmpErr(err)
38+
}
39+
time.Sleep(retryDelay)
40+
continue
41+
}
42+
return nil, err
43+
}
44+
return c, nil
45+
}
46+
}

listener_test.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package retry
2+
3+
import (
4+
"net"
5+
"testing"
6+
7+
"github.com/pkg/errors"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
type testListener struct {
12+
acceptFn func() (net.Conn, error)
13+
}
14+
15+
func newTestListener(acceptFn func() (net.Conn, error)) net.Listener {
16+
return &Listener{
17+
LogTmpErr: func(err error) {},
18+
Listener: &testListener{
19+
acceptFn: acceptFn,
20+
},
21+
}
22+
}
23+
24+
func (l *testListener) Accept() (net.Conn, error) {
25+
return l.acceptFn()
26+
}
27+
28+
func (l *testListener) Close() error {
29+
panic("do not call")
30+
}
31+
32+
func (l *testListener) Addr() net.Addr {
33+
panic("do not call")
34+
}
35+
36+
type testNetError struct {
37+
temporary bool
38+
}
39+
40+
func (e *testNetError) Error() string {
41+
return "test net error"
42+
}
43+
44+
func (e *testNetError) Temporary() bool {
45+
return e.temporary
46+
}
47+
48+
func (e *testNetError) Timeout() bool {
49+
panic("do not call")
50+
}
51+
52+
func TestListener(t *testing.T) {
53+
t.Parallel()
54+
t.Run("general error", func(t *testing.T) {
55+
t.Parallel()
56+
57+
expectedErr := errors.New("general error")
58+
acceptFn := func() (net.Conn, error) {
59+
return nil, expectedErr
60+
}
61+
62+
_, err := newTestListener(acceptFn).Accept()
63+
require.Equal(t, expectedErr, err)
64+
})
65+
t.Run("success", func(t *testing.T) {
66+
t.Parallel()
67+
68+
acceptFn := func() (net.Conn, error) {
69+
return nil, nil
70+
}
71+
72+
_, err := newTestListener(acceptFn).Accept()
73+
require.Nil(t, err)
74+
})
75+
t.Run("non temp net error", func(t *testing.T) {
76+
t.Parallel()
77+
78+
expectedErr := &testNetError{false}
79+
acceptFn := func() (net.Conn, error) {
80+
return nil, expectedErr
81+
}
82+
83+
_, err := newTestListener(acceptFn).Accept()
84+
require.Equal(t, expectedErr, err)
85+
})
86+
t.Run("3x temp net error", func(t *testing.T) {
87+
t.Parallel()
88+
89+
callCount := 0
90+
acceptFn := func() (net.Conn, error) {
91+
callCount++
92+
switch callCount {
93+
case 1:
94+
return nil, &testNetError{true}
95+
case 2:
96+
return nil, &testNetError{true}
97+
case 3:
98+
return nil, nil
99+
default:
100+
t.Fatal("test listener called too many times; callCount: %v", callCount)
101+
panic("unreachable")
102+
}
103+
}
104+
105+
_, err := newTestListener(acceptFn).Accept()
106+
require.Nil(t, err)
107+
require.Equal(t, callCount, 3)
108+
})
109+
}

0 commit comments

Comments
 (0)