Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
52.17% |
24 / 46 |
|
37.50% |
3 / 8 |
CRAP | |
0.00% |
0 / 1 |
MemcachedBagOStuff | |
53.33% |
24 / 45 |
|
37.50% |
3 / 8 |
60.65 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
makeKeyInternal | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
5 | |||
requireConvertGenericKey | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
validateKeyEncoding | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
validateKeyAndPrependRoute | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
stripRouteFromKey | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
fixExpiry | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
doIncrWithInit | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
doIncrWithInitAsync | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
doIncrWithInitSync | n/a |
0 / 0 |
n/a |
0 / 0 |
0 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | * |
18 | * @file |
19 | */ |
20 | namespace Wikimedia\ObjectCache; |
21 | |
22 | use Exception; |
23 | use InvalidArgumentException; |
24 | use RuntimeException; |
25 | |
26 | /** |
27 | * Store data in a memcached server or memcached cluster. |
28 | * |
29 | * This is a base class for MemcachedPhpBagOStuff and MemcachedPeclBagOStuff. |
30 | * |
31 | * @ingroup Cache |
32 | */ |
33 | abstract class MemcachedBagOStuff extends MediumSpecificBagOStuff { |
34 | /** @var string Routing prefix appended to keys during operations */ |
35 | protected $routingPrefix; |
36 | |
37 | /** |
38 | * @param array $params Additional parameters include: |
39 | * - routingPrefix: a routing prefix of the form "<datacenter>/<cluster>/" used to convey |
40 | * the location/strategy to use for handling keys accessed from this instance. The prefix |
41 | * is prepended to keys during cache operations. The memcached proxy must preserve these |
42 | * prefixes in any responses that include requested keys (e.g. get/gets). The proxy is |
43 | * also assumed to strip the routing prefix from the stored key name, which allows for |
44 | * unprefixed access. This can be used with mcrouter. [optional] |
45 | */ |
46 | public function __construct( array $params ) { |
47 | $params['segmentationSize'] ??= 917_504; // < 1MiB |
48 | parent::__construct( $params ); |
49 | |
50 | $this->routingPrefix = $params['routingPrefix'] ?? ''; |
51 | |
52 | // ...and does not use special disk-cache plugins |
53 | $this->attrMap[self::ATTR_DURABILITY] = self::QOS_DURABILITY_SERVICE; |
54 | } |
55 | |
56 | /** |
57 | * Format a cache key. |
58 | * |
59 | * @since 1.27 |
60 | * @see BagOStuff::makeKeyInternal |
61 | * |
62 | * @param string $keyspace |
63 | * @param string[]|int[]|null $components |
64 | * |
65 | * @return string |
66 | */ |
67 | protected function makeKeyInternal( $keyspace, $components ) { |
68 | // Memcached keys have a maximum length of 255 characters. From that, |
69 | // subtract the number of characters we need for the keyspace and for |
70 | // the separator character needed for each argument. To handle some |
71 | // custom prefixes used by thing like WANObjectCache, limit to 205. |
72 | $charsLeft = 205 - strlen( $keyspace ) - count( $components ); |
73 | |
74 | foreach ( $components as &$component ) { |
75 | $component = strtr( $component ?? '', ' ', '_' ); |
76 | |
77 | // Make sure %, #, and non-ASCII chars are escaped |
78 | $component = preg_replace_callback( |
79 | '/[^\x21-\x22\x24\x26-\x39\x3b-\x7e]+/', |
80 | static function ( $m ) { |
81 | return rawurlencode( $m[0] ); |
82 | }, |
83 | $component |
84 | ); |
85 | |
86 | // 33 = 32 characters for the MD5 + 1 for the '#' prefix. |
87 | if ( $charsLeft > 33 && strlen( $component ) > $charsLeft ) { |
88 | $component = '#' . md5( $component ); |
89 | } |
90 | |
91 | $charsLeft -= strlen( $component ); |
92 | } |
93 | unset( $component ); |
94 | |
95 | if ( $charsLeft < 0 ) { |
96 | return $keyspace . ':BagOStuff-long-key:##' . md5( implode( ':', $components ) ); |
97 | } |
98 | |
99 | return $keyspace . ':' . implode( ':', $components ); |
100 | } |
101 | |
102 | protected function requireConvertGenericKey(): bool { |
103 | return true; |
104 | } |
105 | |
106 | /** |
107 | * Ensure that a key is safe to use (contains no control characters and no |
108 | * characters above the ASCII range.) |
109 | * |
110 | * @param string $key |
111 | * |
112 | * @return string |
113 | * @throws Exception |
114 | */ |
115 | public function validateKeyEncoding( $key ) { |
116 | if ( preg_match( '/[^\x21-\x7e]+/', $key ) ) { |
117 | throw new InvalidArgumentException( "Key contains invalid characters: $key" ); |
118 | } |
119 | |
120 | return $key; |
121 | } |
122 | |
123 | /** |
124 | * @param string $key |
125 | * |
126 | * @return string |
127 | */ |
128 | protected function validateKeyAndPrependRoute( $key ) { |
129 | $this->validateKeyEncoding( $key ); |
130 | |
131 | if ( $this->routingPrefix === '' ) { |
132 | return $key; |
133 | } |
134 | |
135 | if ( $key[0] === '/' ) { |
136 | throw new RuntimeException( "Key '$key' already contains a route." ); |
137 | } |
138 | |
139 | return $this->routingPrefix . $key; |
140 | } |
141 | |
142 | /** |
143 | * @param string $key |
144 | * |
145 | * @return string |
146 | */ |
147 | protected function stripRouteFromKey( $key ) { |
148 | if ( $this->routingPrefix === '' ) { |
149 | return $key; |
150 | } |
151 | |
152 | if ( str_starts_with( $key, $this->routingPrefix ) ) { |
153 | return substr( $key, strlen( $this->routingPrefix ) ); |
154 | } |
155 | |
156 | return $key; |
157 | } |
158 | |
159 | /** |
160 | * @param int|float $exptime |
161 | * |
162 | * @return int |
163 | */ |
164 | protected function fixExpiry( $exptime ) { |
165 | if ( $exptime < 0 ) { |
166 | // The PECL driver does not seem to like negative relative values |
167 | $expiresAt = $this->getCurrentTime() + $exptime; |
168 | } elseif ( $this->isRelativeExpiration( $exptime ) ) { |
169 | // TTLs higher than 30 days will be detected as absolute TTLs |
170 | // (UNIX timestamps), and will result in the cache entry being |
171 | // discarded immediately because the expiry is in the past. |
172 | // Clamp expires >30d at 30d, unless they're >=1e9 in which |
173 | // case they are likely to really be absolute (1e9 = 2011-09-09) |
174 | $expiresAt = min( $exptime, self::TTL_MONTH ); |
175 | } else { |
176 | $expiresAt = $exptime; |
177 | } |
178 | |
179 | return (int)$expiresAt; |
180 | } |
181 | |
182 | protected function doIncrWithInit( $key, $exptime, $step, $init, $flags ) { |
183 | if ( $flags & self::WRITE_BACKGROUND ) { |
184 | return $this->doIncrWithInitAsync( $key, $exptime, $step, $init ); |
185 | } else { |
186 | return $this->doIncrWithInitSync( $key, $exptime, $step, $init ); |
187 | } |
188 | } |
189 | |
190 | /** |
191 | * @param string $key |
192 | * @param int $exptime |
193 | * @param int $step |
194 | * @param int $init |
195 | * |
196 | * @return bool True on success, false on failure |
197 | */ |
198 | abstract protected function doIncrWithInitAsync( $key, $exptime, $step, $init ); |
199 | |
200 | /** |
201 | * @param string $key |
202 | * @param int $exptime |
203 | * @param int $step |
204 | * @param int $init |
205 | * |
206 | * @return int|bool New value or false on failure |
207 | */ |
208 | abstract protected function doIncrWithInitSync( $key, $exptime, $step, $init ); |
209 | } |
210 | |
211 | /** @deprecated class alias since 1.43 */ |
212 | class_alias( MemcachedBagOStuff::class, 'MemcachedBagOStuff' ); |