~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
73 lines · 1.3 KB Raw History
package netretry

import (
	"context"
	"errors"
	"io"
	"net"
	"net/http"
	"sync"
	"time"

	"github.com/johnandersen777/socialweb-computer-kcp/internal/kit/authretry"
)

const (
	First = 2 * time.Second

	Cap = 30 * time.Second
)

func Transient(err error) bool {
	if err == nil || errors.Is(err, context.Canceled) {
		return false
	}
	if status := authretry.Status(err); status != 0 {
		return status >= http.StatusInternalServerError || status == http.StatusTooManyRequests
	}
	if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, io.ErrUnexpectedEOF) {
		return true
	}
	var netErr net.Error
	return errors.As(err, &netErr)
}

func After(err error) time.Duration {
	var hinted interface{ RetryAfter() time.Duration }
	if errors.As(err, &hinted) {
		return max(hinted.RetryAfter(), 0)
	}
	return 0
}

func Delay(failures int) time.Duration {
	d := First
	for i := 1; i < failures && d < Cap; i++ {
		d *= 2
	}
	return min(d, Cap)
}

type Tracker[K comparable] struct {
	mu sync.Mutex

	failures map[K]int
}

func NewTracker[K comparable]() *Tracker[K] {
	return &Tracker[K]{failures: map[K]int{}}
}

func (t *Tracker[K]) Fail(key K) (int, time.Duration) {
	t.mu.Lock()
	defer t.mu.Unlock()
	n := t.failures[key] + 1
	t.failures[key] = n
	return n, Delay(n)
}

func (t *Tracker[K]) Reset(key K) {
	t.mu.Lock()
	defer t.mu.Unlock()
	delete(t.failures, key)
}