Completed
Push — master ( 1758f1...601966 )
by Felix
08:03
created

ExponentialBackoff::execute()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 27
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 27
rs 8.439
c 0
b 0
f 0
cc 6
eloc 16
nc 7
nop 2
1
<?php
2
3
namespace SchulzeFelix\SearchConsole;
4
5
6
/**
7
 * Exponential backoff implementation.
8
 */
9
class ExponentialBackoff
10
{
11
    const MAX_DELAY_MICROSECONDS = 120000000;
12
13
    /**
14
     * @var int
15
     */
16
    private $retries;
17
18
    /**
19
     * @var callable
20
     */
21
    private $retryFunction;
22
23
    /**
24
     * @var callable
25
     */
26
    private $delayFunction;
27
28
    /**
29
     * @param int $retries [optional] Number of retries for a failed request.
30
     * @param callable $retryFunction [optional] returns bool for whether or not to retry
31
     */
32
    public function __construct($retries = null, callable $retryFunction = null)
33
    {
34
        $this->retries = $retries !== null ? (int) $retries : 3;
35
        $this->retryFunction = $retryFunction;
36
        $this->delayFunction = function ($delay) {
37
            usleep($delay);
38
        };
39
    }
40
41
    /**
42
     * Executes the retry process.
43
     *
44
     * @param callable $function
45
     * @param array $arguments [optional]
46
     * @return mixed
47
     * @throws \Exception The last exception caught while retrying.
48
     */
49
    public function execute(callable $function, array $arguments = [])
50
    {
51
        $delayFunction = $this->delayFunction;
52
        $retryAttempt = 0;
53
        $exception = null;
54
55
        while (true) {
56
            try {
57
                return call_user_func_array($function, $arguments);
58
            } catch (\Exception $exception) {
59
                if ($this->retryFunction) {
60
                    if (!call_user_func($this->retryFunction, $exception)) {
61
                        throw $exception;
62
                    }
63
                }
64
65
                if ($retryAttempt >= $this->retries) {
66
                    break;
67
                }
68
69
                $delayFunction($this->calculateDelay($retryAttempt));
70
                $retryAttempt++;
71
            }
72
        }
73
74
        throw $exception;
75
    }
76
77
    /**
78
     * @param callable $delayFunction
79
     * @return void
80
     */
81
    public function setDelayFunction(callable $delayFunction)
82
    {
83
        $this->delayFunction = $delayFunction;
84
    }
85
86
    /**
87
     * Calculates exponential delay.
88
     *
89
     * @param int $attempt
90
     * @return int
91
     */
92
    private function calculateDelay($attempt)
93
    {
94
        return min(
95
            mt_rand(0, 1000000) + (pow(2, $attempt) * 1000000),
96
            self::MAX_DELAY_MICROSECONDS
97
        );
98
    }
99
}