1 | <?php |
||
2 | |||
3 | namespace JVelasco\CircuitBreaker\AvailabilityStrategy; |
||
4 | |||
5 | use JVelasco\CircuitBreaker\AvailabilityStrategy; |
||
6 | use JVelasco\CircuitBreaker\StorageException; |
||
7 | |||
8 | class TimeBackoff implements AvailabilityStrategy |
||
9 | { |
||
10 | const ATTEMPTS_KEY = "attempts"; |
||
11 | const LAST_ATTEMPT_TIME_KEY = "last_attempt"; |
||
12 | |||
13 | /** @var Storage */ |
||
14 | protected $storage; |
||
15 | /** @var BackoffStrategy */ |
||
16 | private $backoffStrategy; |
||
17 | /** @var int */ |
||
18 | protected $maxFailures; |
||
19 | /** @var int initial time to wait in milliseconds */ |
||
20 | protected $baseWaitTime; |
||
21 | /** @var int */ |
||
22 | private $maxWaitTime; |
||
23 | |||
24 | 7 | public function __construct( |
|
25 | Storage $storage, |
||
26 | BackoffStrategy $backoffStrategy, |
||
27 | int $maxFailures, |
||
28 | int $baseWaitTime, |
||
29 | int $maxWaitTime |
||
30 | ) { |
||
31 | 7 | $this->storage = $storage; |
|
32 | 7 | $this->maxFailures = $maxFailures; |
|
33 | 7 | $this->baseWaitTime = $baseWaitTime; |
|
34 | 7 | $this->backoffStrategy = $backoffStrategy; |
|
35 | 7 | $this->maxWaitTime = $maxWaitTime; |
|
36 | 7 | } |
|
37 | |||
38 | 7 | public function isAvailable(string $serviceName): bool |
|
39 | { |
||
40 | try { |
||
41 | 7 | if ($this->storage->numberOfFailures($serviceName) < $this->maxFailures) { |
|
42 | 2 | return true; |
|
43 | } |
||
44 | |||
45 | 5 | $attempt = $this->getLastAttempt($serviceName); |
|
46 | 5 | if ($this->millisecondsSinceLastAttempt($serviceName) > $this->waitTime($attempt)) { |
|
47 | 3 | $this->saveAttempt($serviceName, $attempt+1); |
|
48 | 3 | return true; |
|
49 | } |
||
50 | |||
51 | 2 | return false; |
|
52 | 1 | } catch (StorageException $ex) { |
|
53 | 1 | return true; |
|
54 | } |
||
55 | } |
||
56 | |||
57 | 5 | public function getId(): string |
|
58 | { |
||
59 | 5 | return $this->backoffStrategy->id(); |
|
60 | } |
||
61 | |||
62 | 5 | private function getLastAttemptTime(string $serviceName): int |
|
63 | { |
||
64 | 5 | $lastTryTimestamp = $this->storage->getStrategyData( |
|
65 | 5 | $this, |
|
66 | 5 | $serviceName, |
|
67 | 5 | self::LAST_ATTEMPT_TIME_KEY |
|
68 | ); |
||
69 | 5 | return $lastTryTimestamp ? $lastTryTimestamp : $this->now(); |
|
0 ignored issues
–
show
Bug
Best Practice
introduced
by
![]() |
|||
70 | } |
||
71 | |||
72 | 5 | private function getLastAttempt($serviceName): int |
|
73 | { |
||
74 | 5 | return (int) $this->storage->getStrategyData($this, $serviceName, self::ATTEMPTS_KEY); |
|
75 | } |
||
76 | |||
77 | 5 | private function millisecondsSinceLastAttempt(string $serviceName): int |
|
78 | { |
||
79 | 5 | $lastAttempt = $this->getLastAttemptTime($serviceName); |
|
80 | 5 | return $this->now() - $lastAttempt; |
|
81 | } |
||
82 | |||
83 | 5 | private function now(): int |
|
84 | { |
||
85 | 5 | return floor(microtime(true) * 1000); |
|
0 ignored issues
–
show
|
|||
86 | } |
||
87 | |||
88 | 5 | private function waitTime($attempt): int |
|
89 | { |
||
90 | 5 | return min( |
|
91 | 5 | $this->backoffStrategy->waitTime($attempt, $this->baseWaitTime), |
|
92 | 5 | $this->maxWaitTime |
|
93 | ); |
||
94 | } |
||
95 | |||
96 | 3 | private function saveAttempt(string $serviceName, int $attempt) |
|
97 | { |
||
98 | 3 | $this->storage->saveStrategyData( |
|
99 | 3 | $this, |
|
100 | 3 | $serviceName, |
|
101 | 3 | self::ATTEMPTS_KEY, |
|
102 | 3 | $attempt |
|
103 | ); |
||
104 | 3 | $this->storage->saveStrategyData( |
|
105 | 3 | $this, |
|
106 | 3 | $serviceName, |
|
107 | 3 | self::LAST_ATTEMPT_TIME_KEY, |
|
108 | 3 | $this->now() |
|
109 | ); |
||
110 | 3 | $this->storage->resetFailuresCounter($serviceName); |
|
111 | 3 | } |
|
112 | } |
||
113 |