// Package temperrcatcher provides a TempErrCatcher object, // which implements simple error-retrying functionality.
package temperrcatcher import ( ) // 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 ( error) bool { var Temporary := errors.As(, &) return && .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 ( *TempErrCatcher) () { if .Max == 0 { .Max = time.Second } if .IsTemp == nil { .IsTemp = ErrIsTemporary } if .Wait == nil { .Wait = time.Sleep } if .Start == 0 { .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 ( *TempErrCatcher) ( error) bool { .init() if .IsTemp() { := time.Now() if .Sub(.last) > (.delay * 5) { // this is a "new streak" of temp failures. reset. .Reset() } if .delay == 0 { // init case. .delay = .Start } else { .delay *= 2 } if .delay > .Max { .delay = .Max } .Wait(.delay) .last = return true } .Reset() // different failure. call reset return false } // Reset sets the internal delay counter to 0 func ( *TempErrCatcher) () { .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 ( ErrTemporary) () bool { return true } func ( ErrTemporary) () string { return .Err.Error() } func ( ErrTemporary) () string { return .Error() }