PluginProbe
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score / trunk
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score vtrunk
trunk 1.0 1.0.1 1.1 1.1.1 1.1.2 1.2 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 2.0 2.0.1 2.0.2 2.0.3 2.0.4 2.1 2.1.1 2.1.2 2.2 2.2.1 All 69 releases
← All changes | includes/dropins/memcache-object-cache.php +1047 -295 1.2trunk View file →
@@ -1,459 +1,1211 @@
1 1 <?php
2 +/**
3 + * Upstream: https://github.com/poweredcache/wp-memcached
4 + * Upstream version: 92d60df2dabac6a70b863a54d95602a0424fe391
5 + */
2 6 defined( 'ABSPATH' ) || exit;
3 7
4 8 // We need Memcache to continue
5 9 if ( class_exists( 'Memcache' ) ):
6 10
7 -/**
8 - * Copyright (C) 2016 Ryan Boren, Denis de Bernardy, Matt Martz
9 - */
10 11
11 -if ( ! defined( 'WP_CACHE_KEY_SALT' ) ) {
12 - define( 'WP_CACHE_KEY_SALT', DB_NAME );
13 -}
12 + // Users with setups where multiple installs share a common wp-config.php or $table_prefix
13 + // can use this to guarantee uniqueness for the keys generated by this object cache
14 + if ( ! defined( 'WP_CACHE_KEY_SALT' ) ) {
15 + define( 'WP_CACHE_KEY_SALT', '' );
16 + }
14 17
15 -function wp_cache_add( $key, $data, $group = '', $expire = 0 ) {
18 + function wp_cache_add( $key, $data, $group = '', $expire = 0 ) {
19 + global $wp_object_cache;
16 20
17 - global $wp_object_cache;
21 + return $wp_object_cache->add( $key, $data, $group, $expire );
22 + }
18 23
19 - return $wp_object_cache->add( $key, $data, $group, $expire );
20 -}
24 + function wp_cache_add_multiple( array $data, $group = '', $expire = 0 ) {
25 + global $wp_object_cache;
21 26
22 -function wp_cache_incr( $key, $n = 1, $group = '' ) {
27 + return $wp_object_cache->add_multiple( $data, $group, $expire );
28 + }
23 29
24 - global $wp_object_cache;
30 + function wp_cache_incr( $key, $n = 1, $group = '' ) {
31 + global $wp_object_cache;
25 32
26 - return $wp_object_cache->incr( $key, $n, $group );
27 -}
33 + return $wp_object_cache->incr( $key, $n, $group );
34 + }
28 35
29 -function wp_cache_decr( $key, $n = 1, $group = '' ) {
36 + function wp_cache_decr( $key, $n = 1, $group = '' ) {
37 + global $wp_object_cache;
30 38
31 - global $wp_object_cache;
39 + return $wp_object_cache->decr( $key, $n, $group );
40 + }
32 41
33 - return $wp_object_cache->decr( $key, $n, $group );
34 -}
42 + function wp_cache_close() {
43 + global $wp_object_cache;
35 44
36 -function wp_cache_close() {
45 + return $wp_object_cache->close();
46 + }
37 47
38 - global $wp_object_cache;
48 + function wp_cache_delete( $key, $group = '' ) {
49 + global $wp_object_cache;
39 50
40 - return $wp_object_cache->close();
41 -}
51 + return $wp_object_cache->delete( $key, $group );
52 + }
42 53
43 -function wp_cache_delete( $key, $group = '' ) {
54 + function wp_cache_delete_multiple( array $keys, $group = '' ) {
55 + global $wp_object_cache;
44 56
45 - global $wp_object_cache;
57 + return $wp_object_cache->delete_multiple( $keys, $group );
58 + }
46 59
47 - return $wp_object_cache->delete( $key, $group );
48 -}
60 + function wp_cache_flush() {
61 + global $wp_object_cache;
49 62
50 -function wp_cache_flush() {
63 + return $wp_object_cache->flush();
64 + }
51 65
52 - global $wp_object_cache;
66 + function wp_cache_flush_runtime() {
67 + global $wp_object_cache;
53 68
54 - return $wp_object_cache->flush();
55 -}
69 + return $wp_object_cache->flush_runtime();
70 + }
56 71
57 -function wp_cache_get( $key, $group = '', $force = false ) {
72 + function wp_cache_get( $key, $group = '', $force = false, &$found = null ) {
73 + global $wp_object_cache;
58 74
59 - global $wp_object_cache;
75 + if ( function_exists( 'apply_filters' ) ) {
76 + $value = apply_filters( 'pre_wp_cache_get', false, $key, $group, $force );
77 + if ( false !== $value ) {
78 + $found = true;
79 + return $value;
80 + }
81 + }
60 82
61 - return $wp_object_cache->get( $key, $group, $force );
62 -}
83 + return $wp_object_cache->get( $key, $group, $force, $found );
84 + }
63 85
64 -function wp_cache_init() {
86 + function wp_cache_get_multiple( $keys, $group = '', $force = false ) {
87 + global $wp_object_cache;
65 88
66 - global $wp_object_cache;
89 + return $wp_object_cache->get_multiple( $keys, $group, $force );
90 + }
67 91
68 - $wp_object_cache = new WP_Object_Cache();
69 -}
92 + /**
93 + * Retrieve multiple cache entries
94 + *
95 + * @param array $groups Array of arrays, of groups and keys to retrieve
96 + * @return mixed
97 + */
98 + function wp_cache_get_multi( $groups ) {
99 + global $wp_object_cache;
70 100
71 -function wp_cache_replace( $key, $data, $group = '', $expire = 0 ) {
101 + return $wp_object_cache->get_multi( $groups );
102 + }
72 103
73 - global $wp_object_cache;
104 + function wp_cache_init() {
105 + global $wp_object_cache;
74 106
75 - return $wp_object_cache->replace( $key, $data, $group, $expire );
76 -}
107 + $wp_object_cache = new WP_Object_Cache();
108 + }
77 109
78 -function wp_cache_set( $key, $data, $group = '', $expire = 0 ) {
110 + function wp_cache_replace( $key, $data, $group = '', $expire = 0 ) {
111 + global $wp_object_cache;
79 112
80 - global $wp_object_cache;
113 + return $wp_object_cache->replace( $key, $data, $group, $expire );
114 + }
81 115
82 - if ( defined( 'WP_INSTALLING' ) == false ) {
83 - return $wp_object_cache->set( $key, $data, $group, $expire );
84 - } else {
85 - return $wp_object_cache->delete( $key, $group );
116 + function wp_cache_set( $key, $data, $group = '', $expire = 0 ) {
117 + global $wp_object_cache;
118 +
119 + if ( defined( 'WP_INSTALLING' ) === false ) {
120 + return $wp_object_cache->set( $key, $data, $group, $expire );
121 + } else {
122 + return $wp_object_cache->delete( $key, $group );
123 + }
86 124 }
87 -}
88 125
89 -function wp_cache_switch_to_blog( $blog_id ) {
126 + function wp_cache_set_multiple( array $data, $group = '', $expire = 0 ) {
127 + global $wp_object_cache;
90 128
91 - global $wp_object_cache;
129 + return $wp_object_cache->set_multiple( $data, $group, $expire );
130 + }
92 131
93 - return $wp_object_cache->switch_to_blog( $blog_id );
94 -}
132 + function wp_cache_switch_to_blog( $blog_id ) {
133 + global $wp_object_cache;
95 134
96 -function wp_cache_add_global_groups( $groups ) {
135 + return $wp_object_cache->switch_to_blog( $blog_id );
136 + }
97 137
98 - global $wp_object_cache;
138 + function wp_cache_add_global_groups( $groups ) {
139 + global $wp_object_cache;
99 140
100 - $wp_object_cache->add_global_groups( $groups );
101 -}
141 + $wp_object_cache->add_global_groups( $groups );
142 + }
102 143
103 -function wp_cache_add_non_persistent_groups( $groups ) {
144 + function wp_cache_add_non_persistent_groups( $groups ) {
145 + global $wp_object_cache;
104 146
105 - global $wp_object_cache;
147 + $wp_object_cache->add_non_persistent_groups( $groups );
148 + }
106 149
107 - $wp_object_cache->add_non_persistent_groups( $groups );
108 -}
150 + /**
151 + * Determines whether the object cache implementation supports a particular feature.
152 + *
153 + * @param string $feature Name of the feature to check for. Possible values include:
154 + * 'add_multiple', 'set_multiple', 'get_multiple', 'delete_multiple',
155 + * 'flush_runtime', 'flush_group'.
156 + * @return bool True if the feature is supported, false otherwise.
157 + */
158 + function wp_cache_supports( $feature ) {
159 + switch ( $feature ) {
160 + case 'get_multiple':
161 + case 'flush_runtime':
162 + return true;
109 163
110 -class WP_Object_Cache {
111 - var $global_groups = array();
164 + default:
165 + return false;
166 + }
167 + }
112 168
113 - var $no_mc_groups = array();
169 + class WP_Object_Cache {
170 + var $global_groups = array( 'WP_Object_Cache_global' );
114 171
115 - var $cache = array();
116 - var $mc = array();
117 - var $stats = array();
118 - var $group_ops = array();
172 + var $no_mc_groups = array();
119 173
120 - var $cache_enabled = true;
121 - var $default_expiration = 0;
174 + var $cache = array();
175 + /** @var Memcache[] */
176 + var $mc = array();
177 + var $default_mcs = array();
178 + var $stats = array(
179 + 'get' => 0,
180 + 'get_local' => 0,
181 + 'get_multi' => 0,
182 + 'set' => 0,
183 + 'set_local' => 0,
184 + 'add' => 0,
185 + 'delete' => 0,
186 + 'delete_local' => 0,
187 + 'slow-ops' => 0,
188 + );
189 + var $group_ops = array();
190 + var $cache_hits = 0;
191 + var $cache_misses = 0;
192 + var $global_prefix = '';
193 + var $blog_prefix = '';
194 + var $key_salt = '';
122 195
123 - function add( $id, $data, $group = 'default', $expire = 0 ) {
196 + var $flush_group = 'WP_Object_Cache';
197 + var $global_flush_group = 'WP_Object_Cache_global';
198 + var $flush_key = "flush_number_v4";
199 + var $old_flush_key = "flush_number";
200 + var $flush_number = array();
201 + var $global_flush_number = null;
124 202
125 - $key = $this->key( $id, $group );
203 + var $cache_enabled = true;
204 + var $default_expiration = 0;
205 + var $max_expiration = 2592000; // 30 days
126 206
127 - if ( is_object( $data ) ) {
128 - $data = clone $data;
207 + var $stats_callback = null;
208 +
209 + var $connection_errors = array();
210 +
211 + var $time_start = 0;
212 + var $time_total = 0;
213 + var $size_total = 0;
214 + var $slow_op_microseconds = 0.005; // 5 ms
215 +
216 + function add( $id, $data, $group = 'default', $expire = 0 ) {
217 + $key = $this->key( $id, $group );
218 +
219 + if ( is_object( $data ) ) {
220 + $data = clone $data;
221 + }
222 +
223 + if ( in_array( $group, $this->no_mc_groups ) ) {
224 + if ( ! isset( $this->cache[ $key ] ) ) {
225 + $this->cache[ $key ] = [
226 + 'value' => $data,
227 + 'found' => false,
228 + ];
229 +
230 + return true;
231 + }
232 +
233 + return false;
234 + }
235 +
236 + if ( isset( $this->cache[ $key ][ 'value' ] ) && false !== $this->cache[ $key ][ 'value' ] ) {
237 + return false;
238 + }
239 +
240 + $mc = $this->get_mc( $group );
241 +
242 + $expire = intval( $expire );
243 + if ( 0 === $expire || $expire > $this->max_expiration ) {
244 + $expire = $this->default_expiration;
245 + }
246 +
247 + $size = $this->get_data_size( $data );
248 + $this->timer_start();
249 + $result = $mc->add( $key, $data, false, $expire );
250 + $elapsed = $this->timer_stop();
251 +
252 + $comment = '';
253 + if ( isset( $this->cache[ $key ] ) ) {
254 + $comment .= ' [lc already]';
255 + }
256 + if ( false === $result ) {
257 + $comment .= ' [mc already]';
258 + }
259 +
260 + $this->group_ops_stats( 'add', $key, $group, $size, $elapsed, $comment );
261 +
262 + if ( false !== $result ) {
263 + $this->cache[ $key ] = [
264 + 'value' => $data,
265 + 'found' => true,
266 + ];
267 + } else if ( false === $result && true === isset( $this->cache[$key][ 'value' ] ) && false === $this->cache[$key][ 'value' ] ) {
268 + /*
269 + * Here we unset local cache if remote add failed and local cache value is equal to `false` in order
270 + * to update the local cache anytime we get a new information from remote server. This way, the next
271 + * cache get will go to remote server and will fetch recent data.
272 + */
273 + unset( $this->cache[$key] );
274 + }
275 +
276 + return $result;
129 277 }
130 278
131 - if ( in_array( $group, $this->no_mc_groups ) ) {
132 - $this->cache[ $key ] = $data;
133 - return true;
134 - } elseif ( isset( $this->cache[ $key ] ) && $this->cache[ $key ] !== false ) {
135 - return false;
279 + public function add_multiple( array $data, $group = '', $expire = 0 ) {
280 + $values = array();
281 +
282 + foreach ( $data as $key => $value ) {
283 + $values[ $key ] = $this->add( $key, $value, $group, $expire );
284 + }
285 +
286 + return $values;
136 287 }
137 288
138 - $mc =& $this->get_mc( $group );
139 - $expire = ($expire == 0) ? $this->default_expiration : $expire;
140 - $result = $mc->add( $key, $data, false, $expire );
289 + function add_global_groups( $groups ) {
290 + if ( ! is_array( $groups ) ) {
291 + $groups = (array) $groups;
292 + }
141 293
142 - if ( false !== $result ) {
143 - @ ++$this->stats['add'];
144 - $this->group_ops[ $group ][] = "add $id";
145 - $this->cache[ $key ] = $data;
294 + $this->global_groups = array_merge( $this->global_groups, $groups );
295 + $this->global_groups = array_unique( $this->global_groups );
146 296 }
147 297
148 - return $result;
149 - }
298 + function add_non_persistent_groups( $groups ) {
299 + if ( ! is_array( $groups ) ) {
300 + $groups = (array) $groups;
301 + }
150 302
151 - function add_global_groups( $groups ) {
303 + $this->no_mc_groups = array_merge( $this->no_mc_groups, $groups );
304 + $this->no_mc_groups = array_unique( $this->no_mc_groups );
305 + }
152 306
153 - if ( ! is_array( $groups ) ) {
154 - $groups = (array) $groups;
307 + function incr( $id, $n = 1, $group = 'default' ) {
308 + $key = $this->key( $id, $group );
309 + $mc = $this->get_mc( $group );
310 +
311 + $incremented = $mc->increment( $key, $n );
312 +
313 + $this->cache[ $key ] = [
314 + 'value' => $incremented,
315 + 'found' => false !== $incremented,
316 + ];
317 +
318 + return $this->cache[ $key ][ 'value' ];
155 319 }
156 320
157 - $this->global_groups = array_merge( $this->global_groups, $groups );
158 - $this->global_groups = array_unique( $this->global_groups );
159 - }
321 + function decr( $id, $n = 1, $group = 'default' ) {
322 + $key = $this->key( $id, $group );
323 + $mc = $this->get_mc( $group );
160 324
161 - function add_non_persistent_groups( $groups ) {
325 + $decremented = $mc->decrement( $key, $n );
326 + $this->cache[ $key ] = [
327 + 'value' => $decremented,
328 + 'found' => false !== $decremented,
329 + ];
162 330
163 - if ( ! is_array( $groups ) ) {
164 - $groups = (array) $groups;
331 + return $this->cache[ $key ][ 'value' ];
165 332 }
166 333
167 - $this->no_mc_groups = array_merge( $this->no_mc_groups, $groups );
168 - $this->no_mc_groups = array_unique( $this->no_mc_groups );
169 - }
334 + function close() {
335 + foreach ( $this->mc as $bucket => $mc ) {
336 + $mc->close();
337 + }
338 + }
170 339
171 - function incr( $id, $n = 1, $group = 'default' ) {
340 + function delete( $id, $group = 'default' ) {
341 + $key = $this->key( $id, $group );
172 342
173 - $key = $this->key( $id, $group );
174 - $mc =& $this->get_mc( $group );
175 - $this->cache[ $key ] = $mc->increment( $key, $n );
176 - return $this->cache[ $key ];
177 - }
343 + if ( in_array( $group, $this->no_mc_groups ) ) {
344 + unset( $this->cache[ $key ] );
178 345
179 - function decr( $id, $n = 1, $group = 'default' ) {
346 + return true;
347 + }
180 348
181 - $key = $this->key( $id, $group );
182 - $mc =& $this->get_mc( $group );
183 - $this->cache[ $key ] = $mc->decrement( $key, $n );
184 - return $this->cache[ $key ];
185 - }
349 + $mc = $this->get_mc( $group );
186 350
187 - function close() {
351 + $this->timer_start();
352 + $result = $mc->delete( $key );
353 + $elapsed = $this->timer_stop();
188 354
189 - foreach ( $this->mc as $bucket => $mc ) {
190 - $mc->close();
355 + $this->group_ops_stats( 'delete', $key, $group, null, $elapsed );
356 +
357 + if ( false !== $result ) {
358 + unset( $this->cache[ $key ] );
359 + }
360 +
361 + return $result;
191 362 }
192 - }
193 363
194 - function delete( $id, $group = 'default' ) {
364 + public function delete_multiple( array $keys, $group = '' ) {
365 + $values = array();
195 366
196 - $key = $this->key( $id, $group );
367 + foreach ( $keys as $key ) {
368 + $values[ $key ] = $this->delete( $key, $group );
369 + }
197 370
198 - if ( in_array( $group, $this->no_mc_groups ) ) {
199 - unset( $this->cache[ $key ] );
371 + return $values;
372 + }
373 +
374 + // Gets number from all default servers, replicating if needed
375 + function get_max_flush_number( $group ) {
376 + $key = $this->key( $this->flush_key, $group );
377 +
378 + $values = array();
379 + $size = 19; // size of microsecond timestamp serialized
380 + foreach ( $this->default_mcs as $i => $mc ) {
381 + $flags = false;
382 + $this->timer_start();
383 + $values[ $i ] = $mc->get( $key, $flags );
384 + $elapsed = $this->timer_stop();
385 +
386 + if ( empty( $values[ $i ] ) ) {
387 + $this->group_ops_stats( 'get_flush_number', $key, $group, null, $elapsed, 'not_in_memcache' );
388 + } else {
389 + $this->group_ops_stats( 'get_flush_number', $key, $group, $size, $elapsed, 'memcache' );
390 + }
391 + }
392 +
393 + $max = max( $values );
394 +
395 + if ( ! $max > 0 ) {
396 + return false;
397 + }
398 +
399 + // Replicate to servers not having the max.
400 + $expire = 0;
401 + foreach ( $this->default_mcs as $i => $mc ) {
402 + if ( $values[ $i ] < $max ) {
403 + $this->timer_start();
404 + $mc->set( $key, $max, false, $expire );
405 + $elapsed = $this->timer_stop();
406 + $this->group_ops_stats( 'set_flush_number', $key, $group, $size, $elapsed, 'replication_repair' );
407 + }
408 + }
409 +
410 + return $max;
411 + }
412 +
413 + function set_flush_number( $value, $group ) {
414 + $key = $this->key( $this->flush_key, $group );
415 + $expire = 0;
416 + $size = 19;
417 + foreach ( $this->default_mcs as $i => $mc ) {
418 + $this->timer_start();
419 + $mc->set( $key, $value, false, $expire );
420 + $elapsed = $this->timer_stop();
421 + $this->group_ops_stats( 'set_flush_number', $key, $group, $size, $elapsed, 'replication' );
422 + }
423 + }
424 +
425 + function get_flush_number( $group ) {
426 + $flush_number = $this->get_max_flush_number( $group );
427 + // What if there is no v4 flush number?
428 + if ( empty( $flush_number ) ) {
429 + // Look for the v3 flush number key.
430 + $flush_number = intval( $this->get( $this->old_flush_key, $group ) );
431 + if ( !empty( $flush_number ) ) {
432 + // Found v3 flush number. Upgrade to v4 with replication.
433 + $this->set_flush_number( $flush_number, $group );
434 + // Delete v3 key so we can't later restore it and find stale keys.
435 + } else {
436 + // No flush number found anywhere. Make a new one. This flushes the cache.
437 + $flush_number = $this->new_flush_number();
438 + $this->set_flush_number( $flush_number, $group );
439 + }
440 + }
441 +
442 + return $flush_number;
443 + }
444 +
445 + function get_global_flush_number() {
446 + if ( ! isset( $this->global_flush_number ) ) {
447 + $this->global_flush_number = $this->get_flush_number( $this->global_flush_group );
448 + }
449 + return $this->global_flush_number;
450 + }
451 +
452 + function get_blog_flush_number() {
453 + if ( ! isset( $this->flush_number[ $this->blog_prefix ] ) ) {
454 + $this->flush_number[ $this->blog_prefix ] = $this->get_flush_number( $this->flush_group );
455 + }
456 + return $this->flush_number[ $this->blog_prefix ];
457 + }
458 +
459 + function flush_runtime() {
460 + $this->cache = array();
461 + $this->group_ops = array();
462 +
200 463 return true;
201 464 }
202 465
203 - $mc =& $this->get_mc( $group );
466 + function flush() {
467 + // Do not use the memcached flush method. It acts on an
468 + // entire memcached server, affecting all sites.
469 + // Flush is also unusable in some setups, e.g. twemproxy.
470 + // Instead, rotate the key prefix for the current site.
471 + // Global keys are rotated when flushing on any network's
472 + // main site.
473 + $this->cache = array();
204 474
205 - $result = $mc->delete( $key );
475 + $flush_number = $this->new_flush_number();
206 476
207 - @ ++$this->stats['delete'];
208 - $this->group_ops[ $group ][] = "delete $id";
477 + $this->rotate_site_keys( $flush_number );
209 478
210 - if ( false !== $result ) {
211 - unset( $this->cache[ $key ] );
479 + if ( function_exists( 'is_main_site' ) && is_main_site() ) {
480 + $this->rotate_global_keys( $flush_number );
481 + }
482 +
483 + return true;
212 484 }
213 485
214 - return $result;
215 - }
486 + function rotate_site_keys( $flush_number = null ) {
487 + if ( is_null( $flush_number ) ) {
488 + $flush_number = $this->new_flush_number();
489 + }
216 490
217 - function flush() {
218 - $ret = true;
219 - foreach ( array_keys( $this->mc ) as $group ) {
220 - $ret &= $this->mc[ $group ]->flush();
491 + $this->set_flush_number( $flush_number, $this->flush_group );
492 +
493 + $this->flush_number[ $this->blog_prefix ] = $flush_number;
221 494 }
222 - return $ret;
223 - }
224 495
225 - function get( $id, $group = 'default', $force = false ) {
496 + function rotate_global_keys( $flush_number = null ) {
497 + if ( is_null( $flush_number ) ) {
498 + $flush_number = $this->new_flush_number();
499 + }
226 500
227 - $key = $this->key( $id, $group );
228 - $mc =& $this->get_mc( $group );
501 + $this->set_flush_number( $flush_number, $this->global_flush_group );
229 502
230 - if ( isset( $this->cache[ $key ] ) && ( ! $force || in_array( $group, $this->no_mc_groups ) ) ) {
231 - if ( is_object( $this->cache[ $key ] ) ) {
232 - $value = clone $this->cache[ $key ];
503 + $this->global_flush_number = $flush_number;
504 + }
505 +
506 + function new_flush_number() {
507 + return intval( microtime( true ) * 1e6 );
508 + }
509 +
510 + function get( $id, $group = 'default', $force = false, &$found = null ) {
511 + $key = $this->key( $id, $group );
512 + $mc = $this->get_mc( $group );
513 + $found = true;
514 +
515 + if ( isset( $this->cache[ $key ] ) && ( ! $force || in_array( $group, $this->no_mc_groups ) ) ) {
516 + if ( isset( $this->cache[ $key ][ 'value' ] ) && is_object( $this->cache[ $key ][ 'value' ] ) ) {
517 + $value = clone $this->cache[ $key ][ 'value' ];
518 + } else {
519 + $value = $this->cache[ $key ][ 'value' ];
520 + }
521 + $found = $this->cache[ $key ][ 'found' ];
522 +
523 + $this->group_ops_stats( 'get_local', $key, $group, null, null, 'local' );
524 + } else if ( in_array( $group, $this->no_mc_groups ) ) {
525 + $this->cache[ $key ] = [
526 + 'value' => $value = false,
527 + 'found' => false,
528 + ];
529 +
530 + $found = false;
531 +
532 + $this->group_ops_stats( 'get_local', $key, $group, null, null, 'not_in_local' );
233 533 } else {
234 - $value = $this->cache[ $key ];
534 + $flags = false;
535 + $this->timer_start();
536 + $value = $mc->get( $key, $flags );
537 + $elapsed = $this->timer_stop();
538 +
539 + // Value will be unchanged if the key doesn't exist.
540 + if ( false === $flags ) {
541 + $found = false;
542 + $value = false;
543 + } elseif ( false === $value && ( $flags & 0xFF01 ) === 0x01 ) {
544 + /*
545 + * The lowest byte is used for flags.
546 + * 0x01 means the value is serialized (MMC_SERIALIZED).
547 + * The second lowest indicates data type: 0 is string, 1 is bool, 3 is long, 7 is double.
548 + * `null` is serialized into a string, thus $flags is 0x0001
549 + * Empty string will correspond to $flags = 0x0000 (not serialized).
550 + * For `false` or `true` $flags will be 0x0100
551 + *
552 + * See:
553 + * - https://github.com/websupport-sk/pecl-memcache/blob/2a5de3c5d9c0bd0acbcf7e6e0b7570f15f89f55b/php7/memcache_pool.h#L61-L76 - PHP 7.x
554 + * - https://github.com/websupport-sk/pecl-memcache/blob/ccf702b14b18fce18a1863e115a7b4c964df952e/src/memcache_pool.h#L57-L76 - PHP 8.x
555 + *
556 + * In PHP 8, they changed the way memcache_get() handles `null`:
557 + * https://github.com/websupport-sk/pecl-memcache/blob/ccf702b14b18fce18a1863e115a7b4c964df952e/src/memcache.c#L2175-L2177
558 + *
559 + * If the return value is `null`, it is silently converted to `false`. We can only rely upon $flags to find out whether `false` is real.
560 + */
561 + $value = null;
562 + }
563 +
564 + $this->cache[ $key ] = [
565 + 'value' => $value,
566 + 'found' => $found,
567 + ];
568 +
569 + if ( is_null( $value ) || $value === false ) {
570 + $this->group_ops_stats( 'get', $key, $group, null, $elapsed, 'not_in_memcache' );
571 + } else if ( 'checkthedatabaseplease' === $value ) {
572 + $this->group_ops_stats( 'get', $key, $group, null, $elapsed, 'checkthedatabaseplease' );
573 + } else {
574 + $size = $this->get_data_size( $value );
575 + $this->group_ops_stats( 'get', $key, $group, $size, $elapsed, 'memcache' );
576 + }
235 577 }
236 - } else if ( in_array( $group, $this->no_mc_groups ) ) {
237 - $this->cache[ $key ] = $value = false;
238 - } else {
239 - $value = $mc->get( $key );
240 - if ( null === $value ) {
241 - $value = false;
578 +
579 + if ( 'checkthedatabaseplease' === $value ) {
580 + unset( $this->cache[ $key ] );
581 +
582 + $found = false;
583 + $value = false;
242 584 }
243 - $this->cache[ $key ] = $value;
585 +
586 + return $value;
244 587 }
245 588
246 - @ ++$this->stats['get'];
247 - $this->group_ops[ $group ][] = "get $id";
589 + function get_multi( $groups ) {
590 + /*
591 + format: $get['group-name'] = array( 'key1', 'key2' );
592 + */
593 + $return = array();
594 + $return_cache = array(
595 + 'value' => false,
596 + 'found' => false,
597 + );
248 598
249 - if ( 'checkthedatabaseplease' === $value ) {
250 - unset( $this->cache[ $key ] );
251 - $value = false;
599 + foreach ( $groups as $group => $ids ) {
600 + $mc = $this->get_mc( $group );
601 + $keys = array();
602 + $this->timer_start();
603 +
604 + foreach ( $ids as $id ) {
605 + $key = $this->key( $id, $group );
606 + $keys[] = $key;
607 +
608 + if ( isset( $this->cache[ $key ] ) ) {
609 + if ( is_object( $this->cache[ $key ][ 'value'] ) ) {
610 + $return[ $key ] = clone $this->cache[ $key ][ 'value'];
611 + $return_cache[ $key ] = [
612 + 'value' => clone $this->cache[ $key ][ 'value'],
613 + 'found' => $this->cache[ $key ][ 'found'],
614 + ];
615 + } else {
616 + $return[ $key ] = $this->cache[ $key ][ 'value'];
617 + $return_cache[ $key ] = [
618 + 'value' => $this->cache[ $key ][ 'value' ],
619 + 'found' => $this->cache[ $key ][ 'found' ],
620 + ];
621 + }
622 +
623 + continue;
624 + } else if ( in_array( $group, $this->no_mc_groups ) ) {
625 + $return[ $key ] = false;
626 + $return_cache[ $key ] = [
627 + 'value' => false,
628 + 'found' => false,
629 + ];
630 +
631 + continue;
632 + } else {
633 + $fresh_get = $mc->get( $key );
634 + $return[ $key ] = $fresh_get;
635 + $return_cache[ $key ] = [
636 + 'value' => $fresh_get,
637 + 'found' => false !== $fresh_get,
638 + ];
639 + }
640 + }
641 +
642 + $elapsed = $this->timer_stop();
643 + $this->group_ops_stats( 'get_multi', $keys, $group, null, $elapsed );
644 + }
645 +
646 + $this->cache = array_merge( $this->cache, $return_cache );
647 +
648 + return $return;
252 649 }
253 650
254 - return $value;
255 - }
651 + public function get_multiple( $keys, $group = 'default', $force = false ) {
652 + $mc = $this->get_mc( $group );
256 653
257 - function get_multi( $groups ) {
654 + $no_mc = in_array( $group, $this->no_mc_groups, true );
258 655
259 - /*
260 - format: $get['group-name'] = array( 'key1', 'key2' );
261 - */
262 - $return = array();
263 - foreach ( $groups as $group => $ids ) {
264 - $mc =& $this->get_mc( $group );
265 - foreach ( $ids as $id ) {
656 + $uncached_keys = array();
657 + $return = array();
658 + $return_cache = array();
659 +
660 + foreach ( $keys as $id ) {
266 661 $key = $this->key( $id, $group );
267 - if ( isset( $this->cache[ $key ] ) ) {
268 - if ( is_object( $this->cache[ $key ] ) ) {
269 - $return[ $key ] = clone $this->cache[ $key ];
270 - } else {
271 - $return[ $key ] = $this->cache[ $key ];
272 - }
273 - continue;
274 - } else if ( in_array( $group, $this->no_mc_groups ) ) {
275 - $return[ $key ] = false;
276 - continue;
662 +
663 + if ( isset( $this->cache[ $key ] ) && ( ! $force || $no_mc ) ) {
664 + $value = $this->cache[ $key ]['found'] ? $this->cache[ $key ]['value'] : false;
665 + $return[ $id ] = is_object( $value ) ? clone $value : $value;
666 + } else if ( $no_mc ) {
667 + $return[ $id ] = false;
668 + $return_cache[ $key ] = [
669 + 'value' => false,
670 + 'found' => false,
671 + ];
277 672 } else {
278 - $return[ $key ] = $mc->get( $key );
673 + $uncached_keys[ $id ] = $key;
279 674 }
280 675 }
281 - if ( $to_get ) {
282 - $vals = $mc->get_multi( $to_get );
283 - $return = array_merge( $return, $vals );
676 +
677 + if ( $uncached_keys ) {
678 + $this->timer_start();
679 + $uncached_keys_list = array_values( $uncached_keys );
680 + $values = $mc->get( $uncached_keys_list );
681 + $elapsed = $this->timer_stop();
682 +
683 + $this->group_ops_stats( 'get_multiple', $uncached_keys_list, $group, null, $elapsed );
684 +
685 + foreach ( $uncached_keys as $id => $key ) {
686 + $found = array_key_exists( $key, $values );
687 + $value = $found ? $values[ $key ] : false;
688 +
689 + $return[ $id ] = $value;
690 + $return_cache[ $key ] = [
691 + 'value' => $value,
692 + 'found' => $found,
693 + ];
694 + }
284 695 }
696 +
697 + $this->cache = array_merge( $this->cache, $return_cache );
698 +
699 + return $return;
285 700 }
286 - @ ++$this->stats['get_multi'];
287 - $this->group_ops[ $group ][] = "get_multi $id";
288 - $this->cache = array_merge( $this->cache, $return );
289 - return $return;
290 - }
291 701
292 - function key( $key, $group ) {
702 + function flush_prefix( $group ) {
703 + if ( $group === $this->flush_group || $group === $this->global_flush_group ) {
704 + // Never flush the flush numbers.
705 + $number = '_';
706 + } elseif ( false !== array_search( $group, $this->global_groups ) ) {
707 + $number = $this->get_global_flush_number();
708 + } else {
709 + $number = $this->get_blog_flush_number();
710 + }
711 + return $number . ':';
712 + }
293 713
294 - if ( empty( $group ) ) {
295 - $group = 'default';
714 + function key( $key, $group ) {
715 + if ( empty( $group ) ) {
716 + $group = 'default';
717 + }
718 +
719 + $prefix = $this->key_salt;
720 +
721 + $prefix .= $this->flush_prefix( $group );
722 +
723 + if ( false !== array_search( $group, $this->global_groups ) ) {
724 + $prefix .= $this->global_prefix;
725 + } else {
726 + $prefix .= $this->blog_prefix;
727 + }
728 +
729 + return preg_replace( '/\s+/', '', "$prefix:$group:$key" );
296 730 }
297 731
298 - if ( false !== array_search( $group, $this->global_groups ) ) {
299 - $prefix = $this->global_prefix;
300 - } else {
301 - $prefix = $this->blog_prefix;
732 + function replace( $id, $data, $group = 'default', $expire = 0 ) {
733 + $key = $this->key( $id, $group );
734 + $expire = intval( $expire );
735 + if ( 0 === $expire || $expire > $this->max_expiration ) {
736 + $expire = $this->default_expiration;
737 + }
738 + $mc = $this->get_mc( $group );
739 +
740 + if ( is_object( $data ) ) {
741 + $data = clone $data;
742 + }
743 +
744 + $size = $this->get_data_size( $data );
745 + $this->timer_start();
746 + $result = $mc->replace( $key, $data, false, $expire );
747 + $elapsed = $this->timer_stop();
748 + $this->group_ops_stats( 'replace', $key, $group, $size, $elapsed );
749 +
750 + if ( false !== $result ) {
751 + $this->cache[ $key ] = [
752 + 'value' => $data,
753 + 'found' => true,
754 + ];
755 + }
756 +
757 + return $result;
302 758 }
303 759
304 - return preg_replace( '/\s+/', '', WP_CACHE_KEY_SALT . "$prefix$group:$key" );
305 - }
760 + function set( $id, $data, $group = 'default', $expire = 0 ) {
761 + $key = $this->key( $id, $group );
306 762
307 - function replace( $id, $data, $group = 'default', $expire = 0 ) {
763 + if ( isset( $this->cache[ $key ] ) && ( 'checkthedatabaseplease' === $this->cache[ $key ][ 'value' ] ) ) {
764 + return false;
765 + }
308 766
309 - $key = $this->key( $id, $group );
310 - $expire = ($expire == 0) ? $this->default_expiration : $expire;
311 - $mc =& $this->get_mc( $group );
767 + if ( is_object( $data ) ) {
768 + $data = clone $data;
769 + }
312 770
313 - if ( is_object( $data ) ) {
314 - $data = clone $data;
771 + $this->cache[ $key ] = [
772 + 'value' => $data,
773 + 'found' => false, // Set to false as not technically found in memcache at this point.
774 + ];
775 +
776 + if ( in_array( $group, $this->no_mc_groups ) ) {
777 + $this->group_ops_stats( 'set_local', $key, $group, null, null );
778 +
779 + return true;
780 + }
781 +
782 + $expire = intval( $expire );
783 + if ( 0 === $expire || $expire > $this->max_expiration ) {
784 + $expire = $this->default_expiration;
785 + }
786 +
787 + $mc = $this->get_mc( $group );
788 +
789 + $size = $this->get_data_size( $data );
790 + $this->timer_start();
791 + $result = $mc->set( $key, $data, false, $expire );
792 + $elapsed = $this->timer_stop();
793 + $this->group_ops_stats( 'set', $key, $group, $size, $elapsed );
794 +
795 + // Update the found cache value with the result of the set in memcache.
796 + $this->cache[ $key ][ 'found' ] = $result;
797 +
798 + return $result;
315 799 }
316 800
317 - $result = $mc->replace( $key, $data, false, $expire );
318 - if ( false !== $result ) {
319 - $this->cache[ $key ] = $data;
801 + public function set_multiple( array $data, $group = '', $expire = 0 ) {
802 + $values = array();
803 +
804 + foreach ( $data as $key => $value ) {
805 + $values[ $key ] = $this->set( $key, $value, $group, $expire );
806 + }
807 +
808 + return $values;
320 809 }
321 - return $result;
322 - }
323 810
324 - function set( $id, $data, $group = 'default', $expire = 0 ) {
811 + function switch_to_blog( $blog_id ) {
812 + global $table_prefix;
325 813
326 - $key = $this->key( $id, $group );
327 - if ( isset( $this->cache[ $key ] ) && ('checkthedatabaseplease' === $this->cache[ $key ]) ) {
328 - return false;
814 + $blog_id = (int) $blog_id;
815 +
816 + $this->blog_prefix = ( is_multisite() ? $blog_id : $table_prefix );
329 817 }
330 818
331 - if ( is_object( $data ) ) {
332 - $data = clone $data;
819 + function colorize_debug_line( $line, $trailing_html = '' ) {
820 + $colors = array(
821 + 'get' => 'green',
822 + 'get_local' => 'lightgreen',
823 + 'get_multi' => 'fuchsia',
824 + 'get_multiple' => 'navy',
825 + 'set' => 'purple',
826 + 'set_local' => 'orchid',
827 + 'add' => 'blue',
828 + 'delete' => 'red',
829 + 'delete_local' => 'tomato',
830 + 'slow-ops' => 'crimson',
831 + );
832 +
833 + $cmd = substr( $line, 0, strpos( $line, ' ' ) );
834 +
835 + // Start off with a neutral default color...
836 + $color_for_cmd = 'brown';
837 + // And if the cmd has a specific color, use that instead
838 + if ( isset( $colors[ $cmd ] ) ) {
839 + $color_for_cmd = $colors[ $cmd ];
840 + }
841 +
842 + $cmd2 = "<span style='color:" . esc_attr( $color_for_cmd ) . "; font-weight: bold;'>" . esc_html( $cmd ) . "</span>";
843 +
844 + return $cmd2 . esc_html( substr( $line, strlen( $cmd ) ) ) . "$trailing_html\n";
333 845 }
334 846
335 - $this->cache[ $key ] = $data;
847 + function js_toggle() {
848 + echo "
849 + <script>
850 + function memcachedToggleVisibility( id, hidePrefix ) {
851 + var element = document.getElementById( id );
852 + if ( ! element ) {
853 + return;
854 + }
336 855
337 - if ( in_array( $group, $this->no_mc_groups ) ) {
338 - return true;
856 + // Hide all element with `hidePrefix` if given. Used to display only one element at a time.
857 + if ( hidePrefix ) {
858 + var groupStats = document.querySelectorAll( '[id^=\"' + hidePrefix + '\"]' );
859 + groupStats.forEach(
860 + function ( element ) {
861 + element.style.display = 'none';
862 + }
863 + );
864 + }
865 +
866 + // Toggle the one we clicked.
867 + if ( 'none' === element.style.display ) {
868 + element.style.display = 'block';
869 + } else {
870 + element.style.display = 'none';
871 + }
339 872 }
873 + </script>
874 + ";
875 + }
340 876
341 - $expire = ($expire == 0) ? $this->default_expiration : $expire;
342 - $mc =& $this->get_mc( $group );
343 - $result = $mc->set( $key, $data, false, $expire );
877 + /**
878 + * Returns the collected raw stats.
879 + *
880 + * @return array $stats
881 + */
882 + function get_stats() {
883 + $stats = [];
884 + $stats['totals'] = [
885 + 'query_time' => $this->time_total,
886 + 'size' => $this->size_total,
887 + ];
888 + $stats['operation_counts'] = $this->stats;
889 + $stats['operations'] = [];
890 + $stats['groups'] = [];
891 + $stats['slow-ops'] = [];
892 + $stats['slow-ops-groups'] = [];
893 + foreach ( $this->group_ops as $cache_group => $dataset ) {
894 + if ( empty( $cache_group ) ) {
895 + $cache_group = 'default';
896 + }
344 897
345 - return $result;
346 - }
898 + foreach ( $dataset as $data ) {
899 + $operation = $data[0];
900 + $op = [
901 + 'key' => $data[1],
902 + 'size' => $data[2],
903 + 'time' => $data[3],
904 + 'group' => $cache_group,
905 + 'result' => $data[4],
906 + ];
347 907
348 - function switch_to_blog( $blog_id ) {
908 + if ( $cache_group === 'slow-ops' ) {
909 + $key = 'slow-ops';
910 + $groups_key = 'slow-ops-groups';
911 + $op['group'] = $data[5];
912 + $op['backtrace'] = $data[6];
913 + } else {
914 + $key = 'operations';
915 + $groups_key = 'groups';
916 + }
349 917
350 - global $table_prefix;
918 + $stats[ $key ][ $operation ][] = $op;
351 919
352 - $blog_id = (int) $blog_id;
353 - $this->blog_prefix = ( is_multisite() ? $blog_id : $table_prefix ) . ':';
354 - }
920 + if ( ! in_array( $op['group'], $stats[ $groups_key ] ) ) {
921 + $stats[ $groups_key ][] = $op['group'];
922 + }
923 + }
924 + }
355 925
356 - function colorize_debug_line( $line ) {
926 + return $stats;
927 + }
357 928
358 - $colors = array(
359 - 'get' => 'green',
360 - 'set' => 'purple',
361 - 'add' => 'blue',
362 - 'delete' => 'red',
363 - );
364 929
365 - $cmd = substr( $line, 0, strpos( $line, ' ' ) );
930 + function stats() {
931 + $this->js_toggle();
366 932
367 - $cmd2 = "<span style='color:{$colors[$cmd]}'>$cmd</span>";
933 + echo '<h2><span>Total memcache query time:</span>' . number_format_i18n( sprintf( '%0.1f', $this->time_total * 1000 ), 1 ) . ' ms</h2>';
934 + echo "\n";
935 + echo '<h2><span>Total memcache size:</span>' . esc_html( size_format( $this->size_total, 2 ) ) . '</h2>';
936 + echo "\n";
368 937
369 - return $cmd2 . substr( $line, strlen( $cmd ) ) . "\n";
370 - }
938 + foreach ( $this->stats as $stat => $n ) {
939 + if ( empty( $n ) ) {
940 + continue;
941 + }
371 942
372 - function stats() {
943 + echo '<h2>';
944 + echo $this->colorize_debug_line( "$stat $n" );
945 + echo '</h2>';
946 + }
373 947
374 - echo "<p>\n";
375 - foreach ( $this->stats as $stat => $n ) {
376 - echo "<strong>$stat</strong> $n";
377 - echo "<br/>\n";
948 + echo "<ul class='debug-menu-links' style='clear:left;font-size:14px;'>\n";
949 + $groups = array_keys( $this->group_ops );
950 + usort( $groups, 'strnatcasecmp' );
951 +
952 + $active_group = $groups[0];
953 + // Always show `slow-ops` first
954 + if ( in_array( 'slow-ops', $groups ) ) {
955 + $slow_ops_key = array_search( 'slow-ops', $groups );
956 + $slow_ops = $groups[ $slow_ops_key ];
957 + unset( $groups[ $slow_ops_key ] );
958 + array_unshift( $groups, $slow_ops );
959 + $active_group = 'slow-ops';
960 + }
961 +
962 + $total_ops = 0;
963 + $group_titles = array();
964 + foreach ( $groups as $group ) {
965 + $group_name = $group;
966 + if ( empty( $group_name ) ) {
967 + $group_name = 'default';
968 + }
969 + $group_ops = count( $this->group_ops[ $group ] );
970 + $group_size = size_format( array_sum( array_map( function ( $op ) { return $op[2]; }, $this->group_ops[ $group ] ) ), 2 );
971 + $group_time = number_format_i18n( sprintf( '%0.1f', array_sum( array_map( function ( $op ) { return $op[3]; }, $this->group_ops[ $group ] ) ) * 1000 ), 1 );
972 + $total_ops += $group_ops;
973 + $group_title = "{$group_name} [$group_ops][$group_size][{$group_time} ms]";
974 + $group_titles[ $group ] = $group_title;
975 + echo "\t<li><a href='#' onclick='memcachedToggleVisibility( \"object-cache-stats-menu-target-" . esc_js( $group_name ) . "\", \"object-cache-stats-menu-target-\" );'>" . esc_html( $group_title ) . "</a></li>\n";
976 + }
977 + echo "</ul>\n";
978 +
979 + echo "<div id='object-cache-stats-menu-targets'>\n";
980 + foreach ( $groups as $group ) {
981 + $group_name = $group;
982 + if ( empty( $group_name ) ) {
983 + $group_name = 'default';
984 + }
985 + $current = $active_group == $group ? 'style="display: block"' : 'style="display: none"';
986 + echo "<div id='object-cache-stats-menu-target-" . esc_attr( $group_name ) . "' class='object-cache-stats-menu-target' $current>\n";
987 + echo '<h3>' . esc_html( $group_titles[ $group ] ) . '</h3>' . "\n";
988 + echo "<pre>\n";
989 + foreach ( $this->group_ops[ $group ] as $index => $arr ) {
990 + printf( '%3d ', $index );
991 + echo $this->get_group_ops_line( $index, $arr );
992 + }
993 + echo "</pre>\n";
994 + echo "</div>";
995 + }
996 +
997 + echo "</div>";
378 998 }
379 - echo "</p>\n";
380 - echo '<h3>Memcached:</h3>';
381 - foreach ( $this->group_ops as $group => $ops ) {
382 - if ( ! isset( $_GET['debug_queries'] ) && 500 < count( $ops ) ) {
383 - $ops = array_slice( $ops, 0, 500 );
384 - echo "<big>Too many to show! <a href='" . add_query_arg( 'debug_queries', 'true' ) . "'>Show them anyway</a>.</big>\n";
999 +
1000 + function get_group_ops_line( $index, $arr ) {
1001 + // operation
1002 + $line = "{$arr[0]} ";
1003 +
1004 + // key
1005 + $json_encoded_key = json_encode( $arr[1] );
1006 + $line .= $json_encoded_key . " ";
1007 +
1008 + // comment
1009 + if ( ! empty( $arr[4] ) ) {
1010 + $line .= "{$arr[4]} ";
385 1011 }
386 - echo "<h4>$group commands</h4>";
387 - echo "<pre>\n";
388 - $lines = array();
389 - foreach ( $ops as $op ) {
390 - $lines[] = $this->colorize_debug_line( $op );
1012 +
1013 + // size
1014 + if ( isset( $arr[2] ) ) {
1015 + $line .= '(' . size_format( $arr[2], 2 ) . ') ';
391 1016 }
392 - print_r( $lines );
393 - echo "</pre>\n";
1017 +
1018 + // time
1019 + if ( isset( $arr[3] ) ) {
1020 + $line .= '(' . number_format_i18n( sprintf( '%0.1f', $arr[3] * 1000 ), 1 ) . ' ms)';
1021 + }
1022 +
1023 + // backtrace
1024 + $bt_link = '';
1025 + if ( isset( $arr[6] ) ) {
1026 + $key_hash = md5( $index . $json_encoded_key );
1027 + $bt_link = " <small><a href='#' onclick='memcachedToggleVisibility( \"object-cache-stats-debug-$key_hash\" );'>Toggle Backtrace</a></small>";
1028 + $bt_link .= "<pre id='object-cache-stats-debug-$key_hash' style='display:none'>" . esc_html( $arr[6] ) . "</pre>";
1029 + }
1030 +
1031 + return $this->colorize_debug_line( $line, $bt_link );
394 1032 }
395 - }
396 1033
397 - function &get_mc( $group ) {
1034 + /**
1035 + * @param int|string $group
1036 + * @return Memcache
1037 + */
1038 + function get_mc( $group ) {
1039 + if ( isset( $this->mc[ $group ] ) ) {
1040 + return $this->mc[ $group ];
1041 + }
398 1042
399 - if ( isset( $this->mc[ $group ] ) ) {
400 - return $this->mc[ $group ];
1043 + return $this->mc['default'];
401 1044 }
402 - return $this->mc['default'];
403 - }
404 1045
405 - function failure_callback( $host, $port ) {
1046 + function failure_callback( $host, $port ) {
1047 + $this->connection_errors[] = array(
1048 + 'host' => $host,
1049 + 'port' => $port,
1050 + );
1051 + }
406 1052
407 - //error_log("Connection failure for $host:$port\n", 3, '/tmp/memcached.txt');
408 - }
1053 + function salt_keys( $key_salt ) {
1054 + if ( strlen( $key_salt ) ) {
1055 + $this->key_salt = $key_salt . ':';
1056 + } else {
1057 + $this->key_salt = '';
1058 + }
1059 + }
409 1060
410 - function __construct() {
1061 + function __construct() {
1062 + global $memcached_servers;
411 1063
412 - global $memcached_servers;
1064 + if ( isset( $memcached_servers ) ) {
1065 + $buckets = $memcached_servers;
1066 + } else {
1067 + $buckets = array( '127.0.0.1:11211' );
1068 + }
413 1069
414 - if ( isset( $memcached_servers ) ) {
415 - $buckets = $memcached_servers;
416 - } else {
417 - $buckets = array( '127.0.0.1:11211' );
1070 + reset( $buckets );
1071 +
1072 + if ( is_int( key( $buckets ) ) ) {
1073 + $buckets = array( 'default' => $buckets );
1074 + }
1075 +
1076 + foreach ( $buckets as $bucket => $servers ) {
1077 + $this->mc[ $bucket ] = new Memcache();
1078 +
1079 + foreach ( $servers as $i => $server ) {
1080 + if ( 'unix://' == substr( $server, 0, 7 ) ) {
1081 + $node = $server;
1082 + $port = 0;
1083 + } else {
1084 + if ( false === strpos( $server, ':' ) ) {
1085 + $node = $server;
1086 + $port = ini_get( 'memcache.default_port' );
1087 + } else {
1088 + list ( $node, $port ) = explode( ':', $server, 2 );
1089 + }
1090 +
1091 + $port = intval( $port );
1092 +
1093 + if ( ! $port ) {
1094 + $port = 11211;
1095 + }
1096 + }
1097 +
1098 + $this->mc[ $bucket ]->addServer( $node, $port, true, 1, 1, 15, true, array( $this, 'failure_callback' ) );
1099 + $this->mc[ $bucket ]->setCompressThreshold( 20000, 0.2 );
1100 +
1101 + // Prepare individual connections to servers in default bucket for flush_number redundancy
1102 + if ( 'default' === $bucket ) {
1103 + $this->default_mcs[ $i ] = new Memcache();
1104 + $this->default_mcs[ $i ]->addServer( $node, $port, true, 1, 1, 15, true, array( $this, 'failure_callback' ) );
1105 + }
1106 + }
1107 + }
1108 +
1109 + global $blog_id, $table_prefix;
1110 +
1111 + $this->global_prefix = '';
1112 + $this->blog_prefix = '';
1113 +
1114 + if ( function_exists( 'is_multisite' ) ) {
1115 + $this->global_prefix = ( is_multisite() || defined( 'CUSTOM_USER_TABLE' ) && defined( 'CUSTOM_USER_META_TABLE' ) ) ? '' : $table_prefix;
1116 + $this->blog_prefix = ( is_multisite() ? $blog_id : $table_prefix );
1117 + }
1118 +
1119 + $this->salt_keys( WP_CACHE_KEY_SALT );
1120 +
1121 + $this->cache_hits =& $this->stats['get'];
1122 + $this->cache_misses =& $this->stats['add'];
418 1123 }
419 1124
420 - reset( $buckets );
421 - if ( is_int( key( $buckets ) ) ) {
422 - $buckets = array( 'default' => $buckets );
1125 + function increment_stat( $field, $num = 1 ) {
1126 + if ( ! isset( $this->stats[ $field ] ) ) {
1127 + $this->stats[ $field ] = $num;
1128 + } else {
1129 + $this->stats[ $field ] += $num;
1130 + }
423 1131 }
424 1132
425 - foreach ( $buckets as $bucket => $servers ) {
426 - $this->mc[ $bucket ] = new Memcache();
427 - foreach ( $servers as $server ) {
428 - if ( 'unix://' == substr( $server, 0, 7 ) ) {
429 - $node = $server;
430 - $port = 0;
431 - } else {
432 - list ( $node, $port ) = explode( ':', $server );
433 - if ( ! $port ) {
434 - $port = ini_get( 'memcache.default_port' );
435 - }
436 - $port = intval( $port );
437 - if ( ! $port ) {
438 - $port = 11211;
439 - }
1133 + function group_ops_stats( $op, $keys, $group, $size, $time, $comment = '' ) {
1134 + $this->increment_stat( $op );
1135 +
1136 + // we have no use of the local ops details for now
1137 + if ( strpos( $op, '_local' ) ) {
1138 + return;
1139 + }
1140 +
1141 + $this->size_total += $size;
1142 +
1143 + $keys = $this->strip_memcached_keys( $keys );
1144 +
1145 + // @codeCoverageIgnoreStart
1146 + if ( $time > $this->slow_op_microseconds && 'get_multi' !== $op ) {
1147 + $this->increment_stat( 'slow-ops' );
1148 + $backtrace = null;
1149 + if ( function_exists( 'wp_debug_backtrace_summary' ) ) {
1150 + $backtrace = wp_debug_backtrace_summary();
440 1151 }
441 - $this->mc[ $bucket ]->addServer( $node, $port, true, 1, 1, 15, true, array( $this, 'failure_callback' ) );
442 - $this->mc[ $bucket ]->setCompressThreshold( 20000, 0.2 );
1152 + $this->group_ops['slow-ops'][] = array( $op, $keys, $size, $time, $comment, $group, $backtrace );
443 1153 }
1154 + // @codeCoverageIgnoreEnd
1155 +
1156 + $this->group_ops[ $group ][] = array( $op, $keys, $size, $time, $comment );
444 1157 }
445 1158
446 - global $blog_id, $table_prefix;
447 - $this->global_prefix = '';
448 - $this->blog_prefix = '';
449 - if ( function_exists( 'is_multisite' ) ) {
450 - $this->global_prefix = ( is_multisite() || defined( 'CUSTOM_USER_TABLE' ) && defined( 'CUSTOM_USER_META_TABLE' ) ) ? '' : $table_prefix;
451 - $this->blog_prefix = ( is_multisite() ? $blog_id : $table_prefix ) . ':';
1159 + /**
1160 + * Key format: key_salt:flush_number:table_prefix:key_name
1161 + *
1162 + * We want to strip the `key_salt:flush_number` part to not leak the memcached keys.
1163 + * If `key_salt` is set we strip `'key_salt:flush_number`, otherwise just strip the `flush_number` part.
1164 + */
1165 + function strip_memcached_keys( $keys ) {
1166 + if ( ! is_array( $keys ) ) {
1167 + $keys = [ $keys ];
1168 + }
1169 +
1170 + foreach ( $keys as $key => $value ) {
1171 + $offset = 0;
1172 + if ( ! empty( $this->key_salt ) ) {
1173 + $offset = strpos( $value, ':' ) + 1;
1174 + }
1175 +
1176 + $start = strpos( $value, ':', $offset );
1177 + $keys[ $key ] = substr( $value, $start + 1 );
1178 + }
1179 +
1180 + if ( 1 === count( $keys ) ) {
1181 + return $keys[0];
1182 + }
1183 +
1184 + return $keys;
452 1185 }
453 1186
454 - $this->cache_hits =& $this->stats['get'];
455 - $this->cache_misses =& $this->stats['add'];
1187 + function timer_start() {
1188 + $this->time_start = microtime( true );
1189 +
1190 + return true;
1191 + }
1192 +
1193 + function timer_stop() {
1194 + $time_total = microtime( true ) - $this->time_start;
1195 + $this->time_total += $time_total;
1196 +
1197 + return $time_total;
1198 + }
1199 +
1200 + function get_data_size( $data ) {
1201 + if ( is_string( $data ) ) {
1202 + return strlen( $data );
1203 + }
1204 +
1205 + $serialized = serialize( $data );
1206 +
1207 + return strlen( $serialized );
1208 + }
456 1209 }
457 -}
1210 +
458 1211 endif;
459 -?>