mirror of
https://github.com/ipfs/kubo.git
synced 2026-03-01 06:17:56 +08:00
added temp-err-catcher
This commit is contained in:
parent
456719ede7
commit
fd3e0bb37c
4
Godeps/Godeps.json
generated
4
Godeps/Godeps.json
generated
@ -150,6 +150,10 @@
|
||||
"ImportPath": "github.com/jbenet/go-random",
|
||||
"Rev": "2e83344e7dc7898f94501665af34edd4aa95a013"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/jbenet/go-temp-err-catcher",
|
||||
"Rev": "c531232018e678b2a702cfb86b5c3f68d1c8beb8"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/jbenet/goprocess",
|
||||
"Rev": "162148a58668ca38b0b8f0459ccc6ca88e32f1f4"
|
||||
|
||||
9
Godeps/_workspace/src/github.com/jbenet/go-temp-err-catcher/.travis.yml
generated
vendored
Normal file
9
Godeps/_workspace/src/github.com/jbenet/go-temp-err-catcher/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.3
|
||||
- release
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go test -v
|
||||
78
Godeps/_workspace/src/github.com/jbenet/go-temp-err-catcher/README.md
generated
vendored
Normal file
78
Godeps/_workspace/src/github.com/jbenet/go-temp-err-catcher/README.md
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
# go-temp-err-catcher
|
||||
|
||||
This is a little package to use with your net.Listeners.
|
||||
|
||||
Docs: https://godoc.org/github.com/jbenet/go-temp-err-catcher
|
||||
|
||||
Get:
|
||||
|
||||
go get github.com/jbenet/go-temp-err-catcher
|
||||
|
||||
## Examples
|
||||
|
||||
It is meant to be used with things like net.Lister.Accept:
|
||||
|
||||
```go
|
||||
import (
|
||||
tec "github.com/jbenet/go-temp-err-catcher"
|
||||
)
|
||||
|
||||
func listen(listener net.Listener) {
|
||||
var c tec.TempErrCatcher
|
||||
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil && c.IsTemporary(c) {
|
||||
continue
|
||||
}
|
||||
return conn, err
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can make your errors implement `Temporary`:
|
||||
|
||||
```go
|
||||
type errTemp struct {
|
||||
e error
|
||||
}
|
||||
|
||||
func (e errTemp) Temporary() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (e errTemp) Error() string {
|
||||
return e.e.Error()
|
||||
}
|
||||
|
||||
err := errors.New("beep boop")
|
||||
var c tec.TempErrCatcher
|
||||
c.IsTemporary(err) // false
|
||||
c.IsTemporary(errTemp{err}) // true
|
||||
```
|
||||
|
||||
Or just use `ErrTemp`:
|
||||
|
||||
```go
|
||||
err := errors.New("beep boop")
|
||||
var c tec.TempErrCatcher
|
||||
c.IsTemporary(err) // false
|
||||
c.IsTemporary(tec.ErrTemp{err}) // true
|
||||
```
|
||||
|
||||
|
||||
You can also define an `IsTemp` function to classify errors:
|
||||
|
||||
```go
|
||||
var ErrSkip = errors.New("this should be skipped")
|
||||
var ErrNotSkip = errors.New("this should not be skipped")
|
||||
|
||||
var c tec.TempErrCatcher
|
||||
c.IsTemp = func(e error) bool {
|
||||
return e == ErrSkip
|
||||
}
|
||||
|
||||
c.IsTemporary(ErrSkip) // true
|
||||
c.IsTemporary(ErrNotSkip) // false
|
||||
c.IsTemporary(ErrTemp) // false! no longer accepts Temporary()
|
||||
```
|
||||
62
Godeps/_workspace/src/github.com/jbenet/go-temp-err-catcher/doc.go
generated
vendored
Normal file
62
Godeps/_workspace/src/github.com/jbenet/go-temp-err-catcher/doc.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
// Package temperrcatcher provides a TempErrCatcher object,
|
||||
// which implements simple error-retrying functionality.
|
||||
// It is meant to be used with things like net.Lister.Accept:
|
||||
//
|
||||
// import (
|
||||
// tec "github.com/jbenet/go-temp-err-catcher"
|
||||
// )
|
||||
//
|
||||
// func listen(listener net.Listener) {
|
||||
// var c tec.TempErrCatcher
|
||||
//
|
||||
// for {
|
||||
// conn, err := listener.Accept()
|
||||
// if err != nil && c.IsTemporary(c) {
|
||||
// continue
|
||||
// }
|
||||
// return conn, err
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// You can make your errors implement `Temporary`:
|
||||
//
|
||||
// type errTemp struct {
|
||||
// e error
|
||||
// }
|
||||
//
|
||||
// func (e errTemp) Temporary() bool {
|
||||
// return true
|
||||
// }
|
||||
//
|
||||
// func (e errTemp) Error() string {
|
||||
// return e.e.Error()
|
||||
// }
|
||||
//
|
||||
// err := errors.New("beep boop")
|
||||
// var c tec.TempErrCatcher
|
||||
// c.IsTemporary(err) // false
|
||||
// c.IsTemporary(errTemp{err}) // true
|
||||
//
|
||||
// Or just use `ErrTemp`:
|
||||
//
|
||||
// err := errors.New("beep boop")
|
||||
// var c tec.TempErrCatcher
|
||||
// c.IsTemporary(err) // false
|
||||
// c.IsTemporary(tec.ErrTemp{err}) // true
|
||||
//
|
||||
//
|
||||
// You can also define an `IsTemp` function to classify errors:
|
||||
//
|
||||
// var ErrSkip = errors.New("this should be skipped")
|
||||
// var ErrNotSkip = errors.New("this should not be skipped")
|
||||
//
|
||||
// var c tec.TempErrCatcher
|
||||
// c.IsTemp = func(e error) bool {
|
||||
// return e == ErrSkip
|
||||
// }
|
||||
//
|
||||
// c.IsTemporary(ErrSkip) // true
|
||||
// c.IsTemporary(ErrNotSkip) // false
|
||||
// c.IsTemporary(ErrTemp) // false! no longer accepts Temporary()
|
||||
//
|
||||
package temperrcatcher
|
||||
47
Godeps/_workspace/src/github.com/jbenet/go-temp-err-catcher/example/example.go
generated
vendored
Normal file
47
Godeps/_workspace/src/github.com/jbenet/go-temp-err-catcher/example/example.go
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
tec "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-temp-err-catcher"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrTemp = tec.ErrTemporary{fmt.Errorf("ErrTemp")}
|
||||
ErrSkip = fmt.Errorf("ErrSkip")
|
||||
ErrOther = fmt.Errorf("ErrOther")
|
||||
)
|
||||
|
||||
func main() {
|
||||
var normal tec.TempErrCatcher
|
||||
var skipper tec.TempErrCatcher
|
||||
skipper.IsTemp = func(e error) bool {
|
||||
return e == ErrSkip
|
||||
}
|
||||
|
||||
fmt.Println("trying normal (uses Temporary interface)")
|
||||
tryTec(normal)
|
||||
fmt.Println("")
|
||||
fmt.Println("trying skipper (uses our IsTemp function)")
|
||||
tryTec(skipper)
|
||||
}
|
||||
|
||||
func tryTec(c tec.TempErrCatcher) {
|
||||
errs := []error{
|
||||
ErrTemp,
|
||||
ErrSkip,
|
||||
ErrOther,
|
||||
ErrTemp,
|
||||
ErrSkip,
|
||||
ErrOther,
|
||||
}
|
||||
|
||||
for _, e := range errs {
|
||||
if c.IsTemporary(e) {
|
||||
fmt.Printf("\tIsTemporary: true - skipped %s\n", e)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("\tIsTemporary: false - not skipped %s\n", e)
|
||||
}
|
||||
}
|
||||
172
Godeps/_workspace/src/github.com/jbenet/go-temp-err-catcher/tec_test.go
generated
vendored
Normal file
172
Godeps/_workspace/src/github.com/jbenet/go-temp-err-catcher/tec_test.go
generated
vendored
Normal file
@ -0,0 +1,172 @@
|
||||
package temperrcatcher
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrTemp = ErrTemporary{fmt.Errorf("ErrTemp")}
|
||||
ErrSkip = fmt.Errorf("ErrSkip")
|
||||
ErrOther = fmt.Errorf("ErrOther")
|
||||
)
|
||||
|
||||
func testTec(t *testing.T, c TempErrCatcher, errs map[error]bool) {
|
||||
for e, expected := range errs {
|
||||
if c.IsTemporary(e) != expected {
|
||||
t.Error("expected %s to be %v", e, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNil(t *testing.T) {
|
||||
var c TempErrCatcher
|
||||
testTec(t, c, map[error]bool{
|
||||
ErrTemp: true,
|
||||
ErrSkip: false,
|
||||
ErrOther: false,
|
||||
})
|
||||
}
|
||||
|
||||
func TestWait(t *testing.T) {
|
||||
var c TempErrCatcher
|
||||
worked := make(chan time.Duration, 3)
|
||||
c.Wait = func(t time.Duration) {
|
||||
worked <- t
|
||||
}
|
||||
testTec(t, c, map[error]bool{
|
||||
ErrTemp: true,
|
||||
ErrSkip: false,
|
||||
ErrOther: false,
|
||||
})
|
||||
|
||||
// should've called it once
|
||||
select {
|
||||
case <-worked:
|
||||
default:
|
||||
t.Error("did not call our Wait func")
|
||||
}
|
||||
|
||||
// should've called it ONLY once
|
||||
select {
|
||||
case <-worked:
|
||||
t.Error("called our Wait func more than once")
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemporary(t *testing.T) {
|
||||
var c TempErrCatcher
|
||||
testTec(t, c, map[error]bool{
|
||||
ErrTemp: true,
|
||||
ErrSkip: false,
|
||||
ErrOther: false,
|
||||
})
|
||||
}
|
||||
|
||||
func TestDoubles(t *testing.T) {
|
||||
last := time.Now()
|
||||
diff := func() time.Duration {
|
||||
now := time.Now()
|
||||
diff := now.Sub(last)
|
||||
last = now
|
||||
return diff
|
||||
}
|
||||
|
||||
testDiff := func(low, hi time.Duration) {
|
||||
d := diff()
|
||||
grace := time.Duration(time.Microsecond)
|
||||
if (d + grace) < low {
|
||||
t.Error("time difference is smaller than", low, d)
|
||||
}
|
||||
if (d - grace) > hi {
|
||||
t.Error("time difference is greater than", hi, d)
|
||||
}
|
||||
}
|
||||
|
||||
var c TempErrCatcher
|
||||
testDiff(0, c.Start)
|
||||
c.IsTemporary(ErrTemp)
|
||||
testDiff(c.Start, 2*c.Start) // first time.
|
||||
c.IsTemporary(ErrTemp)
|
||||
testDiff(2*c.Start, 4*c.Start) // second time.
|
||||
c.IsTemporary(ErrTemp)
|
||||
testDiff(4*c.Start, 8*c.Start) // third time.
|
||||
}
|
||||
|
||||
func TestDifferentStart(t *testing.T) {
|
||||
last := time.Now()
|
||||
diff := func() time.Duration {
|
||||
now := time.Now()
|
||||
diff := now.Sub(last)
|
||||
last = now
|
||||
return diff
|
||||
}
|
||||
|
||||
testDiff := func(low, hi time.Duration) {
|
||||
d := diff()
|
||||
grace := time.Duration(time.Microsecond)
|
||||
if (d + grace) < low {
|
||||
t.Error("time difference is smaller than", low, d)
|
||||
}
|
||||
if (d - grace) > hi {
|
||||
t.Error("time difference is greater than", hi, d)
|
||||
}
|
||||
}
|
||||
|
||||
var c TempErrCatcher
|
||||
f := time.Millisecond
|
||||
testDiff(0, f)
|
||||
c.IsTemporary(ErrTemp)
|
||||
testDiff(f, 2*f) // first time.
|
||||
c.IsTemporary(ErrTemp)
|
||||
testDiff(2*f, 4*f) // second time.
|
||||
c.IsTemporary(ErrTemp)
|
||||
testDiff(4*f, 8*f) // third time.
|
||||
|
||||
c.Reset()
|
||||
c.Start = 10 * time.Millisecond
|
||||
f = c.Start
|
||||
testDiff(0, f)
|
||||
c.IsTemporary(ErrTemp)
|
||||
testDiff(f, 2*f) // first time.
|
||||
c.IsTemporary(ErrTemp)
|
||||
testDiff(2*f, 4*f) // second time.
|
||||
c.IsTemporary(ErrTemp)
|
||||
testDiff(4*f, 8*f) // third time.
|
||||
}
|
||||
|
||||
func TestDifferentStreaks(t *testing.T) {
|
||||
var c TempErrCatcher
|
||||
// one streak
|
||||
c.IsTemporary(ErrTemp) // 1
|
||||
c.IsTemporary(ErrTemp) // 2
|
||||
c.IsTemporary(ErrTemp) // 4
|
||||
expect := 4 * time.Millisecond
|
||||
if c.delay != expect {
|
||||
t.Error("delay should be:", expect, c.delay)
|
||||
}
|
||||
|
||||
<-time.After(c.delay * 10)
|
||||
|
||||
// a different streak
|
||||
c.IsTemporary(ErrTemp) // 1
|
||||
c.IsTemporary(ErrTemp) // 2
|
||||
c.IsTemporary(ErrTemp) // 4
|
||||
if c.delay != expect {
|
||||
t.Error("delay should be:", expect, c.delay)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFunc(t *testing.T) {
|
||||
var c TempErrCatcher
|
||||
c.IsTemp = func(e error) bool {
|
||||
return e == ErrSkip
|
||||
}
|
||||
testTec(t, c, map[error]bool{
|
||||
ErrTemp: false,
|
||||
ErrSkip: true,
|
||||
ErrOther: false,
|
||||
})
|
||||
}
|
||||
124
Godeps/_workspace/src/github.com/jbenet/go-temp-err-catcher/temp_err_catcher.go
generated
vendored
Normal file
124
Godeps/_workspace/src/github.com/jbenet/go-temp-err-catcher/temp_err_catcher.go
generated
vendored
Normal file
@ -0,0 +1,124 @@
|
||||
// Package temperrcatcher provides a TempErrCatcher object,
|
||||
// which implements simple error-retrying functionality.
|
||||
package temperrcatcher
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// InitialDelay governs how long to wait the first time.
|
||||
// This is defaulted to time.Millisecond, which makes sense
|
||||
// for network listener failures. You may want a much smaller
|
||||
// delay. You can configure this package wide, or in each
|
||||
// TempErrCatcher
|
||||
var InitialDelay = time.Millisecond
|
||||
|
||||
// Temporary is an interface errors can implement to
|
||||
// ensure they are correctly classified by the default
|
||||
// TempErrCatcher classifier
|
||||
type Temporary interface {
|
||||
Temporary() bool
|
||||
}
|
||||
|
||||
// ErrIsTemporary returns whether an error is Temporary(),
|
||||
// iff it implements the Temporary interface.
|
||||
func ErrIsTemporary(e error) bool {
|
||||
te, ok := e.(Temporary)
|
||||
return ok && te.Temporary()
|
||||
}
|
||||
|
||||
// TempErrCatcher catches temporary errors for you. It then sleeps
|
||||
// for a bit before returning (you should then try again). This may
|
||||
// seem odd, but it's exactly what net/http does:
|
||||
// http://golang.org/src/net/http/server.go?s=51504:51550#L1728
|
||||
//
|
||||
// You can set a few options in TempErrCatcher. They all have defaults
|
||||
// so a zero TempErrCatcher is ready to be used:
|
||||
//
|
||||
// var c tec.TempErrCatcher
|
||||
// c.IsTemporary(tempErr)
|
||||
//
|
||||
type TempErrCatcher struct {
|
||||
IsTemp func(error) bool // the classifier to use. default: ErrIsTemporary
|
||||
Wait func(time.Duration) // the wait func to call. default: time.Sleep
|
||||
Max time.Duration // the maximum time to wait. default: time.Second
|
||||
Start time.Duration // the delay to start with. default: InitialDelay
|
||||
delay time.Duration
|
||||
last time.Time
|
||||
}
|
||||
|
||||
func (tec *TempErrCatcher) init() {
|
||||
if tec.Max == 0 {
|
||||
tec.Max = time.Second
|
||||
}
|
||||
if tec.IsTemp == nil {
|
||||
tec.IsTemp = ErrIsTemporary
|
||||
}
|
||||
if tec.Wait == nil {
|
||||
tec.Wait = time.Sleep
|
||||
}
|
||||
if tec.Start == 0 {
|
||||
tec.Start = InitialDelay
|
||||
}
|
||||
}
|
||||
|
||||
// IsTemporary checks whether an error is temporary. It will call
|
||||
// tec.Wait before returning, with a delay. The delay is also
|
||||
// doubled, so we do not constantly spin. This is the strategy
|
||||
// net.Listener uses.
|
||||
//
|
||||
// Note: you will want to call Reset() if you get a success,
|
||||
// so that the stored delay is brough back to 0.
|
||||
func (tec *TempErrCatcher) IsTemporary(e error) bool {
|
||||
tec.init()
|
||||
if tec.IsTemp(e) {
|
||||
now := time.Now()
|
||||
if now.Sub(tec.last) > (tec.delay * 5) {
|
||||
// this is a "new streak" of temp failures. reset.
|
||||
tec.Reset()
|
||||
}
|
||||
|
||||
if tec.delay == 0 { // init case.
|
||||
tec.delay = tec.Start
|
||||
} else {
|
||||
tec.delay *= 2
|
||||
}
|
||||
|
||||
if tec.delay > tec.Max {
|
||||
tec.delay = tec.Max
|
||||
}
|
||||
tec.Wait(tec.delay)
|
||||
tec.last = now
|
||||
return true
|
||||
}
|
||||
tec.Reset() // different failure. call reset
|
||||
return false
|
||||
}
|
||||
|
||||
// Reset sets the internal delay counter to 0
|
||||
func (tec *TempErrCatcher) Reset() {
|
||||
tec.delay = 0
|
||||
}
|
||||
|
||||
// ErrTemporary wraps any error and implements Temporary function.
|
||||
//
|
||||
// err := errors.New("beep boop")
|
||||
// var c tec.TempErrCatcher
|
||||
// c.IsTemporary(err) // false
|
||||
// c.IsTemporary(tec.ErrTemp{err}) // true
|
||||
//
|
||||
type ErrTemporary struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e ErrTemporary) Temporary() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (e ErrTemporary) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
func (e ErrTemporary) String() string {
|
||||
return e.Error()
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user