teldrive/internal/recovery/recovery.go
2023-12-03 01:17:23 +05:30

70 lines
1.3 KiB
Go

package recovery
import (
"context"
"github.com/cenkalti/backoff/v4"
"github.com/go-faster/errors"
"github.com/gotd/td/bin"
"github.com/gotd/td/telegram"
"github.com/gotd/td/tg"
"github.com/gotd/td/tgerr"
)
func hasError(err error, target string) bool {
for err != nil {
if err.Error() == target {
return true
}
if unwrapper, ok := err.(interface{ Unwrap() error }); ok {
err = unwrapper.Unwrap()
} else {
break
}
}
return false
}
type recovery struct {
ctx context.Context
backoff backoff.BackOff
}
func New(ctx context.Context, backoff backoff.BackOff) telegram.Middleware {
return &recovery{
ctx: ctx,
backoff: backoff,
}
}
func (r *recovery) Handle(next tg.Invoker) telegram.InvokeFunc {
return func(ctx context.Context, input bin.Encoder, output bin.Decoder) error {
return backoff.RetryNotify(func() error {
if err := next.Invoke(ctx, input, output); err != nil {
if r.shouldRecover(err) {
return errors.Wrap(err, "recover")
}
return backoff.Permanent(err)
}
return nil
}, r.backoff, nil)
}
}
func (r *recovery) shouldRecover(err error) bool {
select {
case <-r.ctx.Done():
return false
default:
}
//recover only if context is not cancelled and error is not a rpc error
isContextErr := hasError(err, "context canceled")
_, ok := tgerr.As(err)
return !isContextErr && !ok
}