Commit fd3e0bb3 authored by Juan Batiz-Benet's avatar Juan Batiz-Benet

added temp-err-catcher

parent 456719ed
......@@ -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"
......
language: go
go:
- 1.3
- release
- tip
script:
- go test -v
# 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()
```
// 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
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)
}
}
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,
})
}
// 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()
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment