added temp-err-catcher
Juan Batiz-Benet committed
Jan 11, 2015 at 11:57 UTC
fd3e0bb37c0dcb97622635f583c3c23cbc2b9488
7 files changed
+496
Godeps/Godeps.json
+4
@@ -150,6 +150,10 @@
150
"ImportPath": "github.com/jbenet/go-random",
151
"Rev": "2e83344e7dc7898f94501665af34edd4aa95a013"
152
},
153
+ {
154
+ "ImportPath": "github.com/jbenet/go-temp-err-catcher",
155
+ "Rev": "c531232018e678b2a702cfb86b5c3f68d1c8beb8"
156
+ },
157
{
158
"ImportPath": "github.com/jbenet/goprocess",
159
"Rev": "162148a58668ca38b0b8f0459ccc6ca88e32f1f4"
Godeps/_workspace/src/github.com/jbenet/go-temp-err-catcher/.travis.yml
new
+9
@@ -0,0 +1,9 @@
1
+language: go
2
+
3
+go:
4
+ - 1.3
5
+ - release
6
+ - tip
7
+
8
+script:
9
+ - go test -v
Godeps/_workspace/src/github.com/jbenet/go-temp-err-catcher/README.md
new
+78
@@ -0,0 +1,78 @@
1
+# go-temp-err-catcher
2
+
3
+This is a little package to use with your net.Listeners.
4
+
5
+Docs: https://godoc.org/github.com/jbenet/go-temp-err-catcher
6
+
7
+Get:
8
+
9
+ go get github.com/jbenet/go-temp-err-catcher
10
+
11
+## Examples
12
+
13
+It is meant to be used with things like net.Lister.Accept:
14
+
15
+```go
16
+import (
17
+ tec "github.com/jbenet/go-temp-err-catcher"
18
+)
19
+
20
+func listen(listener net.Listener) {
21
+ var c tec.TempErrCatcher
22
+
23
+ for {
24
+ conn, err := listener.Accept()
25
+ if err != nil && c.IsTemporary(c) {
26
+ continue
27
+ }
28
+ return conn, err
29
+ }
30
+}
31
+```
32
+
33
+You can make your errors implement `Temporary`:
34
+
35
+```go
36
+type errTemp struct {
37
+ e error
38
+}
39
+
40
+func (e errTemp) Temporary() bool {
41
+ return true
42
+}
43
+
44
+func (e errTemp) Error() string {
45
+ return e.e.Error()
46
+}
47
+
48
+err := errors.New("beep boop")
49
+var c tec.TempErrCatcher
50
+c.IsTemporary(err) // false
51
+c.IsTemporary(errTemp{err}) // true
52
+```
53
+
54
+Or just use `ErrTemp`:
55
+
56
+```go
57
+err := errors.New("beep boop")
58
+var c tec.TempErrCatcher
59
+c.IsTemporary(err) // false
60
+c.IsTemporary(tec.ErrTemp{err}) // true
61
+```
62
+
63
+
64
+You can also define an `IsTemp` function to classify errors:
65
+
66
+```go
67
+var ErrSkip = errors.New("this should be skipped")
68
+var ErrNotSkip = errors.New("this should not be skipped")
69
+
70
+var c tec.TempErrCatcher
71
+c.IsTemp = func(e error) bool {
72
+ return e == ErrSkip
73
+}
74
+
75
+c.IsTemporary(ErrSkip) // true
76
+c.IsTemporary(ErrNotSkip) // false
77
+c.IsTemporary(ErrTemp) // false! no longer accepts Temporary()
78
+```
Godeps/_workspace/src/github.com/jbenet/go-temp-err-catcher/doc.go
new
+62
@@ -0,0 +1,62 @@
1
+// Package temperrcatcher provides a TempErrCatcher object,
2
+// which implements simple error-retrying functionality.
3
+// It is meant to be used with things like net.Lister.Accept:
4
+//
5
+// import (
6
+// tec "github.com/jbenet/go-temp-err-catcher"
7
+// )
8
+//
9
+// func listen(listener net.Listener) {
10
+// var c tec.TempErrCatcher
11
+//
12
+// for {
13
+// conn, err := listener.Accept()
14
+// if err != nil && c.IsTemporary(c) {
15
+// continue
16
+// }
17
+// return conn, err
18
+// }
19
+// }
20
+//
21
+// You can make your errors implement `Temporary`:
22
+//
23
+// type errTemp struct {
24
+// e error
25
+// }
26
+//
27
+// func (e errTemp) Temporary() bool {
28
+// return true
29
+// }
30
+//
31
+// func (e errTemp) Error() string {
32
+// return e.e.Error()
33
+// }
34
+//
35
+// err := errors.New("beep boop")
36
+// var c tec.TempErrCatcher
37
+// c.IsTemporary(err) // false
38
+// c.IsTemporary(errTemp{err}) // true
39
+//
40
+// Or just use `ErrTemp`:
41
+//
42
+// err := errors.New("beep boop")
43
+// var c tec.TempErrCatcher
44
+// c.IsTemporary(err) // false
45
+// c.IsTemporary(tec.ErrTemp{err}) // true
46
+//
47
+//
48
+// You can also define an `IsTemp` function to classify errors:
49
+//
50
+// var ErrSkip = errors.New("this should be skipped")
51
+// var ErrNotSkip = errors.New("this should not be skipped")
52
+//
53
+// var c tec.TempErrCatcher
54
+// c.IsTemp = func(e error) bool {
55
+// return e == ErrSkip
56
+// }
57
+//
58
+// c.IsTemporary(ErrSkip) // true
59
+// c.IsTemporary(ErrNotSkip) // false
60
+// c.IsTemporary(ErrTemp) // false! no longer accepts Temporary()
61
+//
62
+package temperrcatcher
Godeps/_workspace/src/github.com/jbenet/go-temp-err-catcher/example/example.go
new
+47
@@ -0,0 +1,47 @@
1
+package main
2
+
3
+import (
4
+ "fmt"
5
+
6
+ tec "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-temp-err-catcher"
7
+)
8
+
9
+var (
10
+ ErrTemp = tec.ErrTemporary{fmt.Errorf("ErrTemp")}
11
+ ErrSkip = fmt.Errorf("ErrSkip")
12
+ ErrOther = fmt.Errorf("ErrOther")
13
+)
14
+
15
+func main() {
16
+ var normal tec.TempErrCatcher
17
+ var skipper tec.TempErrCatcher
18
+ skipper.IsTemp = func(e error) bool {
19
+ return e == ErrSkip
20
+ }
21
+
22
+ fmt.Println("trying normal (uses Temporary interface)")
23
+ tryTec(normal)
24
+ fmt.Println("")
25
+ fmt.Println("trying skipper (uses our IsTemp function)")
26
+ tryTec(skipper)
27
+}
28
+
29
+func tryTec(c tec.TempErrCatcher) {
30
+ errs := []error{
31
+ ErrTemp,
32
+ ErrSkip,
33
+ ErrOther,
34
+ ErrTemp,
35
+ ErrSkip,
36
+ ErrOther,
37
+ }
38
+
39
+ for _, e := range errs {
40
+ if c.IsTemporary(e) {
41
+ fmt.Printf("\tIsTemporary: true - skipped %s\n", e)
42
+ continue
43
+ }
44
+
45
+ fmt.Printf("\tIsTemporary: false - not skipped %s\n", e)
46
+ }
47
+}
Godeps/_workspace/src/github.com/jbenet/go-temp-err-catcher/tec_test.go
new
+172
@@ -0,0 +1,172 @@
1
+package temperrcatcher
2
+
3
+import (
4
+ "fmt"
5
+ "testing"
6
+ "time"
7
+)
8
+
9
+var (
10
+ ErrTemp = ErrTemporary{fmt.Errorf("ErrTemp")}
11
+ ErrSkip = fmt.Errorf("ErrSkip")
12
+ ErrOther = fmt.Errorf("ErrOther")
13
+)
14
+
15
+func testTec(t *testing.T, c TempErrCatcher, errs map[error]bool) {
16
+ for e, expected := range errs {
17
+ if c.IsTemporary(e) != expected {
18
+ t.Error("expected %s to be %v", e, expected)
19
+ }
20
+ }
21
+}
22
+
23
+func TestNil(t *testing.T) {
24
+ var c TempErrCatcher
25
+ testTec(t, c, map[error]bool{
26
+ ErrTemp: true,
27
+ ErrSkip: false,
28
+ ErrOther: false,
29
+ })
30
+}
31
+
32
+func TestWait(t *testing.T) {
33
+ var c TempErrCatcher
34
+ worked := make(chan time.Duration, 3)
35
+ c.Wait = func(t time.Duration) {
36
+ worked <- t
37
+ }
38
+ testTec(t, c, map[error]bool{
39
+ ErrTemp: true,
40
+ ErrSkip: false,
41
+ ErrOther: false,
42
+ })
43
+
44
+ // should've called it once
45
+ select {
46
+ case <-worked:
47
+ default:
48
+ t.Error("did not call our Wait func")
49
+ }
50
+
51
+ // should've called it ONLY once
52
+ select {
53
+ case <-worked:
54
+ t.Error("called our Wait func more than once")
55
+ default:
56
+ }
57
+}
58
+
59
+func TestTemporary(t *testing.T) {
60
+ var c TempErrCatcher
61
+ testTec(t, c, map[error]bool{
62
+ ErrTemp: true,
63
+ ErrSkip: false,
64
+ ErrOther: false,
65
+ })
66
+}
67
+
68
+func TestDoubles(t *testing.T) {
69
+ last := time.Now()
70
+ diff := func() time.Duration {
71
+ now := time.Now()
72
+ diff := now.Sub(last)
73
+ last = now
74
+ return diff
75
+ }
76
+
77
+ testDiff := func(low, hi time.Duration) {
78
+ d := diff()
79
+ grace := time.Duration(time.Microsecond)
80
+ if (d + grace) < low {
81
+ t.Error("time difference is smaller than", low, d)
82
+ }
83
+ if (d - grace) > hi {
84
+ t.Error("time difference is greater than", hi, d)
85
+ }
86
+ }
87
+
88
+ var c TempErrCatcher
89
+ testDiff(0, c.Start)
90
+ c.IsTemporary(ErrTemp)
91
+ testDiff(c.Start, 2*c.Start) // first time.
92
+ c.IsTemporary(ErrTemp)
93
+ testDiff(2*c.Start, 4*c.Start) // second time.
94
+ c.IsTemporary(ErrTemp)
95
+ testDiff(4*c.Start, 8*c.Start) // third time.
96
+}
97
+
98
+func TestDifferentStart(t *testing.T) {
99
+ last := time.Now()
100
+ diff := func() time.Duration {
101
+ now := time.Now()
102
+ diff := now.Sub(last)
103
+ last = now
104
+ return diff
105
+ }
106
+
107
+ testDiff := func(low, hi time.Duration) {
108
+ d := diff()
109
+ grace := time.Duration(time.Microsecond)
110
+ if (d + grace) < low {
111
+ t.Error("time difference is smaller than", low, d)
112
+ }
113
+ if (d - grace) > hi {
114
+ t.Error("time difference is greater than", hi, d)
115
+ }
116
+ }
117
+
118
+ var c TempErrCatcher
119
+ f := time.Millisecond
120
+ testDiff(0, f)
121
+ c.IsTemporary(ErrTemp)
122
+ testDiff(f, 2*f) // first time.
123
+ c.IsTemporary(ErrTemp)
124
+ testDiff(2*f, 4*f) // second time.
125
+ c.IsTemporary(ErrTemp)
126
+ testDiff(4*f, 8*f) // third time.
127
+
128
+ c.Reset()
129
+ c.Start = 10 * time.Millisecond
130
+ f = c.Start
131
+ testDiff(0, f)
132
+ c.IsTemporary(ErrTemp)
133
+ testDiff(f, 2*f) // first time.
134
+ c.IsTemporary(ErrTemp)
135
+ testDiff(2*f, 4*f) // second time.
136
+ c.IsTemporary(ErrTemp)
137
+ testDiff(4*f, 8*f) // third time.
138
+}
139
+
140
+func TestDifferentStreaks(t *testing.T) {
141
+ var c TempErrCatcher
142
+ // one streak
143
+ c.IsTemporary(ErrTemp) // 1
144
+ c.IsTemporary(ErrTemp) // 2
145
+ c.IsTemporary(ErrTemp) // 4
146
+ expect := 4 * time.Millisecond
147
+ if c.delay != expect {
148
+ t.Error("delay should be:", expect, c.delay)
149
+ }
150
+
151
+ <-time.After(c.delay * 10)
152
+
153
+ // a different streak
154
+ c.IsTemporary(ErrTemp) // 1
155
+ c.IsTemporary(ErrTemp) // 2
156
+ c.IsTemporary(ErrTemp) // 4
157
+ if c.delay != expect {
158
+ t.Error("delay should be:", expect, c.delay)
159
+ }
160
+}
161
+
162
+func TestFunc(t *testing.T) {
163
+ var c TempErrCatcher
164
+ c.IsTemp = func(e error) bool {
165
+ return e == ErrSkip
166
+ }
167
+ testTec(t, c, map[error]bool{
168
+ ErrTemp: false,
169
+ ErrSkip: true,
170
+ ErrOther: false,
171
+ })
172
+}
Godeps/_workspace/src/github.com/jbenet/go-temp-err-catcher/temp_err_catcher.go
new
+124
@@ -0,0 +1,124 @@
1
+// Package temperrcatcher provides a TempErrCatcher object,
2
+// which implements simple error-retrying functionality.
3
+package temperrcatcher
4
+
5
+import (
6
+ "time"
7
+)
8
+
9
+// InitialDelay governs how long to wait the first time.
10
+// This is defaulted to time.Millisecond, which makes sense
11
+// for network listener failures. You may want a much smaller
12
+// delay. You can configure this package wide, or in each
13
+// TempErrCatcher
14
+var InitialDelay = time.Millisecond
15
+
16
+// Temporary is an interface errors can implement to
17
+// ensure they are correctly classified by the default
18
+// TempErrCatcher classifier
19
+type Temporary interface {
20
+ Temporary() bool
21
+}
22
+
23
+// ErrIsTemporary returns whether an error is Temporary(),
24
+// iff it implements the Temporary interface.
25
+func ErrIsTemporary(e error) bool {
26
+ te, ok := e.(Temporary)
27
+ return ok && te.Temporary()
28
+}
29
+
30
+// TempErrCatcher catches temporary errors for you. It then sleeps
31
+// for a bit before returning (you should then try again). This may
32
+// seem odd, but it's exactly what net/http does:
33
+// http://golang.org/src/net/http/server.go?s=51504:51550#L1728
34
+//
35
+// You can set a few options in TempErrCatcher. They all have defaults
36
+// so a zero TempErrCatcher is ready to be used:
37
+//
38
+// var c tec.TempErrCatcher
39
+// c.IsTemporary(tempErr)
40
+//
41
+type TempErrCatcher struct {
42
+ IsTemp func(error) bool // the classifier to use. default: ErrIsTemporary
43
+ Wait func(time.Duration) // the wait func to call. default: time.Sleep
44
+ Max time.Duration // the maximum time to wait. default: time.Second
45
+ Start time.Duration // the delay to start with. default: InitialDelay
46
+ delay time.Duration
47
+ last time.Time
48
+}
49
+
50
+func (tec *TempErrCatcher) init() {
51
+ if tec.Max == 0 {
52
+ tec.Max = time.Second
53
+ }
54
+ if tec.IsTemp == nil {
55
+ tec.IsTemp = ErrIsTemporary
56
+ }
57
+ if tec.Wait == nil {
58
+ tec.Wait = time.Sleep
59
+ }
60
+ if tec.Start == 0 {
61
+ tec.Start = InitialDelay
62
+ }
63
+}
64
+
65
+// IsTemporary checks whether an error is temporary. It will call
66
+// tec.Wait before returning, with a delay. The delay is also
67
+// doubled, so we do not constantly spin. This is the strategy
68
+// net.Listener uses.
69
+//
70
+// Note: you will want to call Reset() if you get a success,
71
+// so that the stored delay is brough back to 0.
72
+func (tec *TempErrCatcher) IsTemporary(e error) bool {
73
+ tec.init()
74
+ if tec.IsTemp(e) {
75
+ now := time.Now()
76
+ if now.Sub(tec.last) > (tec.delay * 5) {
77
+ // this is a "new streak" of temp failures. reset.
78
+ tec.Reset()
79
+ }
80
+
81
+ if tec.delay == 0 { // init case.
82
+ tec.delay = tec.Start
83
+ } else {
84
+ tec.delay *= 2
85
+ }
86
+
87
+ if tec.delay > tec.Max {
88
+ tec.delay = tec.Max
89
+ }
90
+ tec.Wait(tec.delay)
91
+ tec.last = now
92
+ return true
93
+ }
94
+ tec.Reset() // different failure. call reset
95
+ return false
96
+}
97
+
98
+// Reset sets the internal delay counter to 0
99
+func (tec *TempErrCatcher) Reset() {
100
+ tec.delay = 0
101
+}
102
+
103
+// ErrTemporary wraps any error and implements Temporary function.
104
+//
105
+// err := errors.New("beep boop")
106
+// var c tec.TempErrCatcher
107
+// c.IsTemporary(err) // false
108
+// c.IsTemporary(tec.ErrTemp{err}) // true
109
+//
110
+type ErrTemporary struct {
111
+ Err error
112
+}
113
+
114
+func (e ErrTemporary) Temporary() bool {
115
+ return true
116
+}
117
+
118
+func (e ErrTemporary) Error() string {
119
+ return e.Err.Error()
120
+}
121
+
122
+func (e ErrTemporary) String() string {
123
+ return e.Error()
124
+}