@cryptotaxi247 / kubo / commits / fd3e0bb37

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 +}