1
|
|
|
<?php |
2
|
|
|
namespace Ackintosh\Ganesha\Storage\Adapter; |
3
|
|
|
|
4
|
|
|
use Ackintosh\Ganesha; |
5
|
|
|
use Ackintosh\Ganesha\Configuration; |
6
|
|
|
use Ackintosh\Ganesha\Exception\StorageException; |
7
|
|
|
use Ackintosh\Ganesha\Storage; |
8
|
|
|
use Ackintosh\Ganesha\Storage\AdapterInterface; |
9
|
|
|
use RuntimeException; |
10
|
|
|
|
11
|
|
|
class Memcached implements AdapterInterface, TumblingTimeWindowInterface |
12
|
|
|
{ |
13
|
|
|
/** |
14
|
|
|
* @var \Memcached |
15
|
|
|
*/ |
16
|
|
|
private $memcached; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* @var Configuration |
20
|
|
|
*/ |
21
|
|
|
private $configuration; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Memcached constructor. |
25
|
|
|
* @param \Memcached $memcached |
26
|
|
|
*/ |
27
|
|
|
public function __construct(\Memcached $memcached) |
28
|
|
|
{ |
29
|
|
|
// initial_value in (increment|decrement) requires \Memcached::OPT_BINARY_PROTOCOL |
30
|
|
|
$memcached->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); |
31
|
|
|
$this->memcached = $memcached; |
32
|
|
|
} |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @return bool |
36
|
|
|
*/ |
37
|
|
|
public function supportCountStrategy(): bool |
38
|
|
|
{ |
39
|
|
|
return true; |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @return bool |
44
|
|
|
*/ |
45
|
|
|
public function supportRateStrategy(): bool |
46
|
|
|
{ |
47
|
|
|
return true; |
48
|
|
|
} |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* @param Configuration $configuration |
52
|
|
|
* @return void |
53
|
|
|
*/ |
54
|
|
|
public function setConfiguration(Configuration $configuration): void |
55
|
|
|
{ |
56
|
|
|
$this->configuration = $configuration; |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* @param string $service |
61
|
|
|
* @return int |
62
|
|
|
* @throws StorageException |
63
|
|
|
*/ |
64
|
|
|
public function load(string $service): int |
65
|
|
|
{ |
66
|
|
|
$r = (int)$this->memcached->get($service); |
67
|
|
|
$this->throwExceptionIfErrorOccurred(); |
68
|
|
|
return $r; |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* @param string $service |
73
|
|
|
* @param int $count |
74
|
|
|
* @return void |
75
|
|
|
* @throws StorageException |
76
|
|
|
*/ |
77
|
|
|
public function save(string $service, int $count): void |
78
|
|
|
{ |
79
|
|
|
if (!$this->memcached->set($service, $count)) { |
80
|
|
|
throw new StorageException('failed to set the value : ' . $this->memcached->getResultMessage()); |
81
|
|
|
} |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* @param string $service |
86
|
|
|
* @return void |
87
|
|
|
* @throws StorageException |
88
|
|
|
*/ |
89
|
|
|
public function increment(string $service): void |
90
|
|
|
{ |
91
|
|
|
// requires \Memcached::OPT_BINARY_PROTOCOL |
92
|
|
|
if ($this->memcached->increment($service, 1, 1) === false) { |
93
|
|
|
throw new StorageException('failed to increment failure count : ' . $this->memcached->getResultMessage()); |
94
|
|
|
} |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* @param string $service |
99
|
|
|
* @return void |
100
|
|
|
* @throws StorageException |
101
|
|
|
*/ |
102
|
|
|
public function decrement(string $service): void |
103
|
|
|
{ |
104
|
|
|
// requires \Memcached::OPT_BINARY_PROTOCOL |
105
|
|
|
if ($this->memcached->decrement($service, 1, 0) === false) { |
106
|
|
|
throw new StorageException('failed to decrement failure count : ' . $this->memcached->getResultMessage()); |
107
|
|
|
} |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* @param string $service |
112
|
|
|
* @param int $lastFailureTime |
113
|
|
|
* @throws StorageException |
114
|
|
|
*/ |
115
|
|
|
public function saveLastFailureTime(string $service, int $lastFailureTime): void |
116
|
|
|
{ |
117
|
|
|
if (!$this->memcached->set($service, $lastFailureTime)) { |
118
|
|
|
throw new StorageException('failed to set the last failure time : ' . $this->memcached->getResultMessage()); |
119
|
|
|
} |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* @param string $service |
124
|
|
|
* @return int |
125
|
|
|
* @throws StorageException |
126
|
|
|
*/ |
127
|
|
|
public function loadLastFailureTime(string $service): int |
128
|
|
|
{ |
129
|
|
|
$r = $this->memcached->get($service); |
130
|
|
|
$this->throwExceptionIfErrorOccurred(); |
131
|
|
|
return $r; |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* @param string $service |
136
|
|
|
* @param int $status |
137
|
|
|
* @throws StorageException |
138
|
|
|
*/ |
139
|
|
|
public function saveStatus(string $service, int $status): void |
140
|
|
|
{ |
141
|
|
|
if (!$this->memcached->set($service, $status)) { |
142
|
|
|
throw new StorageException('failed to set the status : ' . $this->memcached->getResultMessage()); |
143
|
|
|
} |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* @param string $service |
148
|
|
|
* @return int |
149
|
|
|
* @throws StorageException |
150
|
|
|
*/ |
151
|
|
|
public function loadStatus(string $service): int |
152
|
|
|
{ |
153
|
|
|
$status = $this->memcached->get($service); |
154
|
|
|
$this->throwExceptionIfErrorOccurred(); |
155
|
|
|
if ($status === false && $this->memcached->getResultCode() === \Memcached::RES_NOTFOUND) { |
156
|
|
|
$this->saveStatus($service, Ganesha::STATUS_CALMED_DOWN); |
157
|
|
|
return Ganesha::STATUS_CALMED_DOWN; |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
return $status; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
public function reset(): void |
164
|
|
|
{ |
165
|
|
|
if (!$this->memcached->getStats()) { |
166
|
|
|
throw new RuntimeException("Couldn't connect to memcached."); |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
// getAllKeys() with OPT_BINARY_PROTOCOL is not suppoted. |
170
|
|
|
// So temporarily disable it. |
171
|
|
|
$this->memcached->setOption(\Memcached::OPT_BINARY_PROTOCOL, false); |
172
|
|
|
$keys = $this->memcached->getAllKeys(); |
173
|
|
|
$this->memcached->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); |
174
|
|
|
if (!$keys) { |
|
|
|
|
175
|
|
|
$resultCode = $this->memcached->getResultCode(); |
176
|
|
|
if ($resultCode === 0) { |
177
|
|
|
// no keys |
178
|
|
|
return; |
179
|
|
|
} |
180
|
|
|
$message = sprintf( |
181
|
|
|
'failed to get memcached keys. resultCode: %d, resultMessage: %s', |
182
|
|
|
$resultCode, |
183
|
|
|
$this->memcached->getResultMessage() |
184
|
|
|
); |
185
|
|
|
throw new RuntimeException($message); |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
foreach ($keys as $k) { |
189
|
|
|
if ($this->isGaneshaData($k)) { |
190
|
|
|
$this->memcached->delete($k); |
191
|
|
|
} |
192
|
|
|
} |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
public function isGaneshaData(string $key): bool |
196
|
|
|
{ |
197
|
|
|
$regex = sprintf( |
198
|
|
|
'#\A%s.+(%s|%s|%s|%s|%s)\z#', |
199
|
|
|
Storage\StorageKeys::KEY_PREFIX, |
200
|
|
|
Storage\StorageKeys::KEY_SUFFIX_SUCCESS, |
201
|
|
|
Storage\StorageKeys::KEY_SUFFIX_FAILURE, |
202
|
|
|
Storage\StorageKeys::KEY_SUFFIX_REJECTION, |
203
|
|
|
Storage\StorageKeys::KEY_SUFFIX_LAST_FAILURE_TIME, |
204
|
|
|
Storage\StorageKeys::KEY_SUFFIX_STATUS |
205
|
|
|
); |
206
|
|
|
|
207
|
|
|
return preg_match($regex, $key) === 1; |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
/** |
211
|
|
|
* Throws an exception if some error occurs in memcached. |
212
|
|
|
* |
213
|
|
|
* @return void |
214
|
|
|
* @throws StorageException |
215
|
|
|
*/ |
216
|
|
|
private function throwExceptionIfErrorOccurred(): void |
217
|
|
|
{ |
218
|
|
|
$errorResultCodes = [ |
219
|
|
|
\Memcached::RES_FAILURE, |
220
|
|
|
\Memcached::RES_SERVER_TEMPORARILY_DISABLED, |
221
|
|
|
\Memcached::RES_SERVER_MEMORY_ALLOCATION_FAILURE, |
222
|
|
|
]; |
223
|
|
|
|
224
|
|
|
if (in_array($this->memcached->getResultCode(), $errorResultCodes, true)) { |
225
|
|
|
throw new StorageException($this->memcached->getResultMessage()); |
226
|
|
|
} |
227
|
|
|
} |
228
|
|
|
} |
229
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.