Lines 100.00% 16 / 16
Methods 100.00% 4 / 4
Classes 100.00% 1 / 1
Name Lines Methods CRAP
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 delayFor 100.00% 4 / 4 100.00% 1 / 1 2
 sumDelaysUpTo 100.00% 10 / 10 100.00% 1 / 1 5
 isDelayFinalAt 100.00% 1 / 1 100.00% 1 / 1 2
9final readonly class BackoffPolicy
10{
11    private const int MAX_DOUBLINGS = 40;
12
13    public function __construct(
14        private Duration $initialDelay,
15        private Duration $maxDelay,
16    ) {}
17
18    public function delayFor(int $attempt): Duration
19    {
20        if ($attempt < 1) {
21            return Duration::zero();
22        }
23
24        $doubled = $this->initialDelay->toMicroseconds() * 2 ** min($attempt - 1, self::MAX_DOUBLINGS);
25
26        return Duration::microseconds((int) min($doubled, $this->maxDelay->toMicroseconds()));
27    }
28
29    public function sumDelaysUpTo(int $attempt): Duration
30    {
31        $total = 0;
32        $next = 1;
33
34        while ($next <= $attempt) {
35            $delay = $this->delayFor($next)->toMicroseconds();
36            $repeats = $this->isDelayFinalAt($next) ? $attempt - $next + 1 : 1;
37
38            if ($delay > 0 && $repeats > intdiv(\PHP_INT_MAX - $total, $delay)) {
39                return Duration::microseconds(\PHP_INT_MAX);
40            }
41
42            $total += $delay * $repeats;
43            $next += $repeats;
44        }
45
46        return Duration::microseconds($total);
47    }
48
49    private function isDelayFinalAt(int $attempt): bool
50    {
51        return $attempt > self::MAX_DOUBLINGS || !$this->maxDelay->isLongerThan($this->delayFor($attempt));
52    }
53}