MediaWiki REL1_32
WANObjectCache.php
Go to the documentation of this file.
1<?php
22use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
23use Psr\Log\LoggerAwareInterface;
24use Psr\Log\LoggerInterface;
25use Psr\Log\NullLogger;
26
118class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
120 protected $cache;
122 protected $processCaches = [];
124 protected $purgeChannel;
126 protected $purgeRelayer;
128 protected $mcrouterAware;
130 protected $region;
132 protected $cluster;
134 protected $logger;
136 protected $stats;
140 protected $asyncHandler;
142 protected $epoch;
143
145 protected $lastRelayError = self::ERR_NONE;
146
148 private $callbackDepth = 0;
150 private $warmupCache = [];
152 private $warmupKeyMisses = 0;
153
156
160 const MAX_READ_LAG = 7;
162 const HOLDOFF_TTL = 11; // MAX_COMMIT_DELAY + MAX_READ_LAG + 1
163
165 const CHECK_KEY_TTL = self::TTL_YEAR;
168
170 const LOCK_TTL = 10;
172 const LOW_TTL = 30;
173
175 const AGE_NEW = 60;
177 const HOT_TTR = 900;
179 const HIT_RATE_HIGH = 1;
181 const RAMPUP_TTL = 30;
182
184 const TTL_UNCACHEABLE = -1;
186 const TSE_NONE = -1;
188 const TTL_LAGGED = 30;
190 const HOLDOFF_NONE = 0;
192 const STALE_TTL_NONE = 0;
194 const GRACE_TTL_NONE = 0;
195
198
200 const TINY_NEGATIVE = -0.000001;
201
203 const VERSION = 1;
204
205 const FLD_VERSION = 0; // key to cache version number
206 const FLD_VALUE = 1; // key to the cached value
207 const FLD_TTL = 2; // key to the original TTL
208 const FLD_TIME = 3; // key to the cache time
209 const FLD_FLAGS = 4; // key to the flags bitfield
210 const FLD_HOLDOFF = 5; // key to any hold-off TTL
211
213 const FLG_STALE = 1;
214
215 const ERR_NONE = 0; // no error
216 const ERR_NO_RESPONSE = 1; // no response
217 const ERR_UNREACHABLE = 2; // can't connect
218 const ERR_UNEXPECTED = 3; // response gave some error
219 const ERR_RELAY = 4; // relay broadcast failed
220
221 const VALUE_KEY_PREFIX = 'WANCache:v:';
222 const INTERIM_KEY_PREFIX = 'WANCache:i:';
223 const TIME_KEY_PREFIX = 'WANCache:t:';
224 const MUTEX_KEY_PREFIX = 'WANCache:m:';
225
226 const PURGE_VAL_PREFIX = 'PURGED:';
227
228 const VFLD_DATA = 'WOC:d'; // key to the value of versioned data
229 const VFLD_VERSION = 'WOC:v'; // key to the version of the value present
230
231 const PC_PRIMARY = 'primary:1000'; // process cache name and max key count
232
233 const DEFAULT_PURGE_CHANNEL = 'wancache-purge';
234
261 public function __construct( array $params ) {
262 $this->cache = $params['cache'];
263 $this->purgeChannel = $params['channels']['purge'] ?? self::DEFAULT_PURGE_CHANNEL;
264 $this->purgeRelayer = $params['relayers']['purge'] ?? new EventRelayerNull( [] );
265 $this->region = $params['region'] ?? 'main';
266 $this->cluster = $params['cluster'] ?? 'wan-main';
267 $this->mcrouterAware = !empty( $params['mcrouterAware'] );
268 $this->epoch = $params['epoch'] ?? 1.0;
269
270 $this->setLogger( $params['logger'] ?? new NullLogger() );
271 $this->stats = $params['stats'] ?? new NullStatsdDataFactory();
272 $this->asyncHandler = $params['asyncHandler'] ?? null;
273 }
274
278 public function setLogger( LoggerInterface $logger ) {
279 $this->logger = $logger;
280 }
281
287 public static function newEmpty() {
288 return new static( [
289 'cache' => new EmptyBagOStuff()
290 ] );
291 }
292
334 final public function get( $key, &$curTTL = null, array $checkKeys = [], &$asOf = null ) {
335 $curTTLs = [];
336 $asOfs = [];
337 $values = $this->getMulti( [ $key ], $curTTLs, $checkKeys, $asOfs );
338 $curTTL = $curTTLs[$key] ?? null;
339 $asOf = $asOfs[$key] ?? null;
340
341 return $values[$key] ?? false;
342 }
343
356 final public function getMulti(
357 array $keys, &$curTTLs = [], array $checkKeys = [], array &$asOfs = []
358 ) {
359 $result = [];
360 $curTTLs = [];
361 $asOfs = [];
362
363 $vPrefixLen = strlen( self::VALUE_KEY_PREFIX );
364 $valueKeys = self::prefixCacheKeys( $keys, self::VALUE_KEY_PREFIX );
365
366 $checkKeysForAll = [];
367 $checkKeysByKey = [];
368 $checkKeysFlat = [];
369 foreach ( $checkKeys as $i => $checkKeyGroup ) {
370 $prefixed = self::prefixCacheKeys( (array)$checkKeyGroup, self::TIME_KEY_PREFIX );
371 $checkKeysFlat = array_merge( $checkKeysFlat, $prefixed );
372 // Is this check keys for a specific cache key, or for all keys being fetched?
373 if ( is_int( $i ) ) {
374 $checkKeysForAll = array_merge( $checkKeysForAll, $prefixed );
375 } else {
376 $checkKeysByKey[$i] = isset( $checkKeysByKey[$i] )
377 ? array_merge( $checkKeysByKey[$i], $prefixed )
378 : $prefixed;
379 }
380 }
381
382 // Fetch all of the raw values
383 $keysGet = array_merge( $valueKeys, $checkKeysFlat );
384 if ( $this->warmupCache ) {
385 $wrappedValues = array_intersect_key( $this->warmupCache, array_flip( $keysGet ) );
386 $keysGet = array_diff( $keysGet, array_keys( $wrappedValues ) ); // keys left to fetch
387 $this->warmupKeyMisses += count( $keysGet );
388 } else {
389 $wrappedValues = [];
390 }
391 if ( $keysGet ) {
392 $wrappedValues += $this->cache->getMulti( $keysGet );
393 }
394 // Time used to compare/init "check" keys (derived after getMulti() to be pessimistic)
395 $now = $this->getCurrentTime();
396
397 // Collect timestamps from all "check" keys
398 $purgeValuesForAll = $this->processCheckKeys( $checkKeysForAll, $wrappedValues, $now );
399 $purgeValuesByKey = [];
400 foreach ( $checkKeysByKey as $cacheKey => $checks ) {
401 $purgeValuesByKey[$cacheKey] =
402 $this->processCheckKeys( $checks, $wrappedValues, $now );
403 }
404
405 // Get the main cache value for each key and validate them
406 foreach ( $valueKeys as $vKey ) {
407 if ( !isset( $wrappedValues[$vKey] ) ) {
408 continue; // not found
409 }
410
411 $key = substr( $vKey, $vPrefixLen ); // unprefix
412
413 list( $value, $curTTL ) = $this->unwrap( $wrappedValues[$vKey], $now );
414 if ( $value !== false ) {
415 $result[$key] = $value;
416 // Force dependent keys to be seen as stale for a while after purging
417 // to reduce race conditions involving stale data getting cached
418 $purgeValues = $purgeValuesForAll;
419 if ( isset( $purgeValuesByKey[$key] ) ) {
420 $purgeValues = array_merge( $purgeValues, $purgeValuesByKey[$key] );
421 }
422 foreach ( $purgeValues as $purge ) {
423 $safeTimestamp = $purge[self::FLD_TIME] + $purge[self::FLD_HOLDOFF];
424 if ( $safeTimestamp >= $wrappedValues[$vKey][self::FLD_TIME] ) {
425 // How long ago this value was invalidated by *this* check key
426 $ago = min( $purge[self::FLD_TIME] - $now, self::TINY_NEGATIVE );
427 // How long ago this value was invalidated by *any* known check key
428 $curTTL = min( $curTTL, $ago );
429 }
430 }
431 }
432 $curTTLs[$key] = $curTTL;
433 $asOfs[$key] = ( $value !== false ) ? $wrappedValues[$vKey][self::FLD_TIME] : null;
434 }
435
436 return $result;
437 }
438
446 private function processCheckKeys( array $timeKeys, array $wrappedValues, $now ) {
447 $purgeValues = [];
448 foreach ( $timeKeys as $timeKey ) {
449 $purge = isset( $wrappedValues[$timeKey] )
450 ? $this->parsePurgeValue( $wrappedValues[$timeKey] )
451 : false;
452 if ( $purge === false ) {
453 // Key is not set or malformed; regenerate
454 $newVal = $this->makePurgeValue( $now, self::HOLDOFF_TTL );
455 $this->cache->add( $timeKey, $newVal, self::CHECK_KEY_TTL );
456 $purge = $this->parsePurgeValue( $newVal );
457 }
458 $purgeValues[] = $purge;
459 }
460 return $purgeValues;
461 }
462
528 final public function set( $key, $value, $ttl = 0, array $opts = [] ) {
529 $now = $this->getCurrentTime();
530 $lockTSE = $opts['lockTSE'] ?? self::TSE_NONE;
531 $staleTTL = $opts['staleTTL'] ?? self::STALE_TTL_NONE;
532 $age = isset( $opts['since'] ) ? max( 0, $now - $opts['since'] ) : 0;
533 $lag = $opts['lag'] ?? 0;
534
535 // Do not cache potentially uncommitted data as it might get rolled back
536 if ( !empty( $opts['pending'] ) ) {
537 $this->logger->info( 'Rejected set() for {cachekey} due to pending writes.',
538 [ 'cachekey' => $key ] );
539
540 return true; // no-op the write for being unsafe
541 }
542
543 $wrapExtra = []; // additional wrapped value fields
544 // Check if there's a risk of writing stale data after the purge tombstone expired
545 if ( $lag === false || ( $lag + $age ) > self::MAX_READ_LAG ) {
546 // Case A: read lag with "lockTSE"; save but record value as stale
547 if ( $lockTSE >= 0 ) {
548 $ttl = max( 1, (int)$lockTSE ); // set() expects seconds
549 $wrapExtra[self::FLD_FLAGS] = self::FLG_STALE; // mark as stale
550 // Case B: any long-running transaction; ignore this set()
551 } elseif ( $age > self::MAX_READ_LAG ) {
552 $this->logger->info( 'Rejected set() for {cachekey} due to snapshot lag.',
553 [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ] );
554
555 return true; // no-op the write for being unsafe
556 // Case C: high replication lag; lower TTL instead of ignoring all set()s
557 } elseif ( $lag === false || $lag > self::MAX_READ_LAG ) {
558 $ttl = $ttl ? min( $ttl, self::TTL_LAGGED ) : self::TTL_LAGGED;
559 $this->logger->warning( 'Lowered set() TTL for {cachekey} due to replication lag.',
560 [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ] );
561 // Case D: medium length request with medium replication lag; ignore this set()
562 } else {
563 $this->logger->info( 'Rejected set() for {cachekey} due to high read lag.',
564 [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ] );
565
566 return true; // no-op the write for being unsafe
567 }
568 }
569
570 // Wrap that value with time/TTL/version metadata
571 $wrapped = $this->wrap( $value, $ttl, $now ) + $wrapExtra;
572
573 $func = function ( $cache, $key, $cWrapped ) use ( $wrapped ) {
574 return ( is_string( $cWrapped ) )
575 ? false // key is tombstoned; do nothing
576 : $wrapped;
577 };
578
579 return $this->cache->merge( self::VALUE_KEY_PREFIX . $key, $func, $ttl + $staleTTL, 1 );
580 }
581
643 final public function delete( $key, $ttl = self::HOLDOFF_TTL ) {
644 $key = self::VALUE_KEY_PREFIX . $key;
645
646 if ( $ttl <= 0 ) {
647 // Publish the purge to all datacenters
648 $ok = $this->relayDelete( $key );
649 } else {
650 // Publish the purge to all datacenters
651 $ok = $this->relayPurge( $key, $ttl, self::HOLDOFF_NONE );
652 }
653
654 return $ok;
655 }
656
676 final public function getCheckKeyTime( $key ) {
677 return $this->getMultiCheckKeyTime( [ $key ] )[$key];
678 }
679
741 final public function getMultiCheckKeyTime( array $keys ) {
742 $rawKeys = [];
743 foreach ( $keys as $key ) {
744 $rawKeys[$key] = self::TIME_KEY_PREFIX . $key;
745 }
746
747 $rawValues = $this->cache->getMulti( $rawKeys );
748 $rawValues += array_fill_keys( $rawKeys, false );
749
750 $times = [];
751 foreach ( $rawKeys as $key => $rawKey ) {
752 $purge = $this->parsePurgeValue( $rawValues[$rawKey] );
753 if ( $purge !== false ) {
754 $time = $purge[self::FLD_TIME];
755 } else {
756 // Casting assures identical floats for the next getCheckKeyTime() calls
757 $now = (string)$this->getCurrentTime();
758 $this->cache->add(
759 $rawKey,
760 $this->makePurgeValue( $now, self::HOLDOFF_TTL ),
761 self::CHECK_KEY_TTL
762 );
763 $time = (float)$now;
764 }
765
766 $times[$key] = $time;
767 }
768
769 return $times;
770 }
771
806 final public function touchCheckKey( $key, $holdoff = self::HOLDOFF_TTL ) {
807 // Publish the purge to all datacenters
808 return $this->relayPurge( self::TIME_KEY_PREFIX . $key, self::CHECK_KEY_TTL, $holdoff );
809 }
810
838 final public function resetCheckKey( $key ) {
839 // Publish the purge to all datacenters
840 return $this->relayDelete( self::TIME_KEY_PREFIX . $key );
841 }
842
1090 final public function getWithSetCallback( $key, $ttl, $callback, array $opts = [] ) {
1091 $pcTTL = $opts['pcTTL'] ?? self::TTL_UNCACHEABLE;
1092
1093 // Try the process cache if enabled and the cache callback is not within a cache callback.
1094 // Process cache use in nested callbacks is not lag-safe with regard to HOLDOFF_TTL since
1095 // the in-memory value is further lagged than the shared one since it uses a blind TTL.
1096 if ( $pcTTL >= 0 && $this->callbackDepth == 0 ) {
1097 $group = $opts['pcGroup'] ?? self::PC_PRIMARY;
1098 $procCache = $this->getProcessCache( $group );
1099 $value = $procCache->has( $key, $pcTTL ) ? $procCache->get( $key ) : false;
1100 } else {
1101 $procCache = false;
1102 $value = false;
1103 }
1104
1105 if ( $value === false ) {
1106 // Fetch the value over the network
1107 if ( isset( $opts['version'] ) ) {
1108 $version = $opts['version'];
1109 $asOf = null;
1110 $cur = $this->doGetWithSetCallback(
1111 $key,
1112 $ttl,
1113 function ( $oldValue, &$ttl, &$setOpts, $oldAsOf )
1114 use ( $callback, $version ) {
1115 if ( is_array( $oldValue )
1116 && array_key_exists( self::VFLD_DATA, $oldValue )
1117 && array_key_exists( self::VFLD_VERSION, $oldValue )
1118 && $oldValue[self::VFLD_VERSION] === $version
1119 ) {
1120 $oldData = $oldValue[self::VFLD_DATA];
1121 } else {
1122 // VFLD_DATA is not set if an old, unversioned, key is present
1123 $oldData = false;
1124 $oldAsOf = null;
1125 }
1126
1127 return [
1128 self::VFLD_DATA => $callback( $oldData, $ttl, $setOpts, $oldAsOf ),
1129 self::VFLD_VERSION => $version
1130 ];
1131 },
1132 $opts,
1133 $asOf
1134 );
1135 if ( $cur[self::VFLD_VERSION] === $version ) {
1136 // Value created or existed before with version; use it
1137 $value = $cur[self::VFLD_DATA];
1138 } else {
1139 // Value existed before with a different version; use variant key.
1140 // Reflect purges to $key by requiring that this key value be newer.
1142 $this->makeGlobalKey( 'WANCache-key-variant', md5( $key ), $version ),
1143 $ttl,
1144 $callback,
1145 // Regenerate value if not newer than $key
1146 [ 'version' => null, 'minAsOf' => $asOf ] + $opts
1147 );
1148 }
1149 } else {
1150 $value = $this->doGetWithSetCallback( $key, $ttl, $callback, $opts );
1151 }
1152
1153 // Update the process cache if enabled
1154 if ( $procCache && $value !== false ) {
1155 $procCache->set( $key, $value );
1156 }
1157 }
1158
1159 return $value;
1160 }
1161
1175 protected function doGetWithSetCallback( $key, $ttl, $callback, array $opts, &$asOf = null ) {
1176 $lowTTL = $opts['lowTTL'] ?? min( self::LOW_TTL, $ttl );
1177 $lockTSE = $opts['lockTSE'] ?? self::TSE_NONE;
1178 $staleTTL = $opts['staleTTL'] ?? self::STALE_TTL_NONE;
1179 $graceTTL = $opts['graceTTL'] ?? self::GRACE_TTL_NONE;
1180 $checkKeys = $opts['checkKeys'] ?? [];
1181 $busyValue = $opts['busyValue'] ?? null;
1182 $popWindow = $opts['hotTTR'] ?? self::HOT_TTR;
1183 $ageNew = $opts['ageNew'] ?? self::AGE_NEW;
1184 $minTime = $opts['minAsOf'] ?? self::MIN_TIMESTAMP_NONE;
1185 $versioned = isset( $opts['version'] );
1186
1187 // Get a collection name to describe this class of key
1188 $kClass = $this->determineKeyClass( $key );
1189
1190 // Get the current key value
1191 $curTTL = null;
1192 $cValue = $this->get( $key, $curTTL, $checkKeys, $asOf ); // current value
1193 $value = $cValue; // return value
1194
1195 $preCallbackTime = $this->getCurrentTime();
1196 // Determine if a cached value regeneration is needed or desired
1197 if ( $value !== false
1198 && $this->isAliveOrInGracePeriod( $curTTL, $graceTTL )
1199 && $this->isValid( $value, $versioned, $asOf, $minTime )
1200 ) {
1201 $preemptiveRefresh = (
1202 $this->worthRefreshExpiring( $curTTL, $lowTTL ) ||
1203 $this->worthRefreshPopular( $asOf, $ageNew, $popWindow, $preCallbackTime )
1204 );
1205
1206 if ( !$preemptiveRefresh ) {
1207 $this->stats->increment( "wanobjectcache.$kClass.hit.good" );
1208
1209 return $value;
1210 } elseif ( $this->asyncHandler ) {
1211 // Update the cache value later, such during post-send of an HTTP request
1212 $func = $this->asyncHandler;
1213 $func( function () use ( $key, $ttl, $callback, $opts, $asOf ) {
1214 $opts['minAsOf'] = INF; // force a refresh
1215 $this->doGetWithSetCallback( $key, $ttl, $callback, $opts, $asOf );
1216 } );
1217 $this->stats->increment( "wanobjectcache.$kClass.hit.refresh" );
1218
1219 return $value;
1220 }
1221 }
1222
1223 // A deleted key with a negative TTL left must be tombstoned
1224 $isTombstone = ( $curTTL !== null && $value === false );
1225 if ( $isTombstone && $lockTSE <= 0 ) {
1226 // Use the INTERIM value for tombstoned keys to reduce regeneration load
1227 $lockTSE = self::INTERIM_KEY_TTL;
1228 }
1229 // Assume a key is hot if requested soon after invalidation
1230 $isHot = ( $curTTL !== null && $curTTL <= 0 && abs( $curTTL ) <= $lockTSE );
1231 // Use the mutex if there is no value and a busy fallback is given
1232 $checkBusy = ( $busyValue !== null && $value === false );
1233 // Decide whether a single thread should handle regenerations.
1234 // This avoids stampedes when $checkKeys are bumped and when preemptive
1235 // renegerations take too long. It also reduces regenerations while $key
1236 // is tombstoned. This balances cache freshness with avoiding DB load.
1237 $useMutex = ( $isHot || ( $isTombstone && $lockTSE > 0 ) || $checkBusy );
1238
1239 $lockAcquired = false;
1240 if ( $useMutex ) {
1241 // Acquire a datacenter-local non-blocking lock
1242 if ( $this->cache->add( self::MUTEX_KEY_PREFIX . $key, 1, self::LOCK_TTL ) ) {
1243 // Lock acquired; this thread should update the key
1244 $lockAcquired = true;
1245 } elseif ( $value !== false && $this->isValid( $value, $versioned, $asOf, $minTime ) ) {
1246 $this->stats->increment( "wanobjectcache.$kClass.hit.stale" );
1247 // If it cannot be acquired; then the stale value can be used
1248 return $value;
1249 } else {
1250 // Use the INTERIM value for tombstoned keys to reduce regeneration load.
1251 // For hot keys, either another thread has the lock or the lock failed;
1252 // use the INTERIM value from the last thread that regenerated it.
1253 $value = $this->getInterimValue( $key, $versioned, $minTime, $asOf );
1254 if ( $value !== false ) {
1255 $this->stats->increment( "wanobjectcache.$kClass.hit.volatile" );
1256
1257 return $value;
1258 }
1259 // Use the busy fallback value if nothing else
1260 if ( $busyValue !== null ) {
1261 $miss = is_infinite( $minTime ) ? 'renew' : 'miss';
1262 $this->stats->increment( "wanobjectcache.$kClass.$miss.busy" );
1263
1264 return is_callable( $busyValue ) ? $busyValue() : $busyValue;
1265 }
1266 }
1267 }
1268
1269 if ( !is_callable( $callback ) ) {
1270 throw new InvalidArgumentException( "Invalid cache miss callback provided." );
1271 }
1272
1273 // Generate the new value from the callback...
1274 $setOpts = [];
1275 ++$this->callbackDepth;
1276 try {
1277 $value = call_user_func_array( $callback, [ $cValue, &$ttl, &$setOpts, $asOf ] );
1278 } finally {
1279 --$this->callbackDepth;
1280 }
1281 $valueIsCacheable = ( $value !== false && $ttl >= 0 );
1282
1283 // When delete() is called, writes are write-holed by the tombstone,
1284 // so use a special INTERIM key to pass the new value around threads.
1285 if ( ( $isTombstone && $lockTSE > 0 ) && $valueIsCacheable ) {
1286 $tempTTL = max( 1, (int)$lockTSE ); // set() expects seconds
1287 $newAsOf = $this->getCurrentTime();
1288 $wrapped = $this->wrap( $value, $tempTTL, $newAsOf );
1289 // Avoid using set() to avoid pointless mcrouter broadcasting
1290 $this->setInterimValue( $key, $wrapped, $tempTTL );
1291 }
1292
1293 if ( $valueIsCacheable ) {
1294 $setOpts['lockTSE'] = $lockTSE;
1295 $setOpts['staleTTL'] = $staleTTL;
1296 // Use best known "since" timestamp if not provided
1297 $setOpts += [ 'since' => $preCallbackTime ];
1298 // Update the cache; this will fail if the key is tombstoned
1299 $this->set( $key, $value, $ttl, $setOpts );
1300 }
1301
1302 if ( $lockAcquired ) {
1303 // Avoid using delete() to avoid pointless mcrouter broadcasting
1304 $this->cache->changeTTL( self::MUTEX_KEY_PREFIX . $key, (int)$preCallbackTime - 60 );
1305 }
1306
1307 $miss = is_infinite( $minTime ) ? 'renew' : 'miss';
1308 $this->stats->increment( "wanobjectcache.$kClass.$miss.compute" );
1309
1310 return $value;
1311 }
1312
1320 protected function getInterimValue( $key, $versioned, $minTime, &$asOf ) {
1321 if ( !$this->useInterimHoldOffCaching ) {
1322 return false; // disabled
1323 }
1324
1325 $wrapped = $this->cache->get( self::INTERIM_KEY_PREFIX . $key );
1326 list( $value ) = $this->unwrap( $wrapped, $this->getCurrentTime() );
1327 if ( $value !== false && $this->isValid( $value, $versioned, $asOf, $minTime ) ) {
1328 $asOf = $wrapped[self::FLD_TIME];
1329
1330 return $value;
1331 }
1332
1333 return false;
1334 }
1335
1341 protected function setInterimValue( $key, $wrapped, $tempTTL ) {
1342 $this->cache->merge(
1343 self::INTERIM_KEY_PREFIX . $key,
1344 function () use ( $wrapped ) {
1345 return $wrapped;
1346 },
1347 $tempTTL,
1348 1
1349 );
1350 }
1351
1418 final public function getMultiWithSetCallback(
1419 ArrayIterator $keyedIds, $ttl, callable $callback, array $opts = []
1420 ) {
1421 $valueKeys = array_keys( $keyedIds->getArrayCopy() );
1422 $checkKeys = $opts['checkKeys'] ?? [];
1423 $pcTTL = $opts['pcTTL'] ?? self::TTL_UNCACHEABLE;
1424
1425 // Load required keys into process cache in one go
1426 $this->warmupCache = $this->getRawKeysForWarmup(
1427 $this->getNonProcessCachedKeys( $valueKeys, $opts, $pcTTL ),
1428 $checkKeys
1429 );
1430 $this->warmupKeyMisses = 0;
1431
1432 // Wrap $callback to match the getWithSetCallback() format while passing $id to $callback
1433 $id = null; // current entity ID
1434 $func = function ( $oldValue, &$ttl, &$setOpts, $oldAsOf ) use ( $callback, &$id ) {
1435 return $callback( $id, $oldValue, $ttl, $setOpts, $oldAsOf );
1436 };
1437
1438 $values = [];
1439 foreach ( $keyedIds as $key => $id ) { // preserve order
1440 $values[$key] = $this->getWithSetCallback( $key, $ttl, $func, $opts );
1441 }
1442
1443 $this->warmupCache = [];
1444
1445 return $values;
1446 }
1447
1513 final public function getMultiWithUnionSetCallback(
1514 ArrayIterator $keyedIds, $ttl, callable $callback, array $opts = []
1515 ) {
1516 $idsByValueKey = $keyedIds->getArrayCopy();
1517 $valueKeys = array_keys( $idsByValueKey );
1518 $checkKeys = $opts['checkKeys'] ?? [];
1519 $pcTTL = $opts['pcTTL'] ?? self::TTL_UNCACHEABLE;
1520 unset( $opts['lockTSE'] ); // incompatible
1521 unset( $opts['busyValue'] ); // incompatible
1522
1523 // Load required keys into process cache in one go
1524 $keysGet = $this->getNonProcessCachedKeys( $valueKeys, $opts, $pcTTL );
1525 $this->warmupCache = $this->getRawKeysForWarmup( $keysGet, $checkKeys );
1526 $this->warmupKeyMisses = 0;
1527
1528 // IDs of entities known to be in need of regeneration
1529 $idsRegen = [];
1530
1531 // Find out which keys are missing/deleted/stale
1532 $curTTLs = [];
1533 $asOfs = [];
1534 $curByKey = $this->getMulti( $keysGet, $curTTLs, $checkKeys, $asOfs );
1535 foreach ( $keysGet as $key ) {
1536 if ( !array_key_exists( $key, $curByKey ) || $curTTLs[$key] < 0 ) {
1537 $idsRegen[] = $idsByValueKey[$key];
1538 }
1539 }
1540
1541 // Run the callback to populate the regeneration value map for all required IDs
1542 $newSetOpts = [];
1543 $newTTLsById = array_fill_keys( $idsRegen, $ttl );
1544 $newValsById = $idsRegen ? $callback( $idsRegen, $newTTLsById, $newSetOpts ) : [];
1545
1546 // Wrap $callback to match the getWithSetCallback() format while passing $id to $callback
1547 $id = null; // current entity ID
1548 $func = function ( $oldValue, &$ttl, &$setOpts, $oldAsOf )
1549 use ( $callback, &$id, $newValsById, $newTTLsById, $newSetOpts )
1550 {
1551 if ( array_key_exists( $id, $newValsById ) ) {
1552 // Value was already regerated as expected, so use the value in $newValsById
1553 $newValue = $newValsById[$id];
1554 $ttl = $newTTLsById[$id];
1555 $setOpts = $newSetOpts;
1556 } else {
1557 // Pre-emptive/popularity refresh and version mismatch cases are not detected
1558 // above and thus $newValsById has no entry. Run $callback on this single entity.
1559 $ttls = [ $id => $ttl ];
1560 $newValue = $callback( [ $id ], $ttls, $setOpts )[$id];
1561 $ttl = $ttls[$id];
1562 }
1563
1564 return $newValue;
1565 };
1566
1567 // Run the cache-aside logic using warmupCache instead of persistent cache queries
1568 $values = [];
1569 foreach ( $idsByValueKey as $key => $id ) { // preserve order
1570 $values[$key] = $this->getWithSetCallback( $key, $ttl, $func, $opts );
1571 }
1572
1573 $this->warmupCache = [];
1574
1575 return $values;
1576 }
1577
1590 final public function reap( $key, $purgeTimestamp, &$isStale = false ) {
1591 $minAsOf = $purgeTimestamp + self::HOLDOFF_TTL;
1592 $wrapped = $this->cache->get( self::VALUE_KEY_PREFIX . $key );
1593 if ( is_array( $wrapped ) && $wrapped[self::FLD_TIME] < $minAsOf ) {
1594 $isStale = true;
1595 $this->logger->warning( "Reaping stale value key '$key'." );
1596 $ttlReap = self::HOLDOFF_TTL; // avoids races with tombstone creation
1597 $ok = $this->cache->changeTTL( self::VALUE_KEY_PREFIX . $key, $ttlReap );
1598 if ( !$ok ) {
1599 $this->logger->error( "Could not complete reap of key '$key'." );
1600 }
1601
1602 return $ok;
1603 }
1604
1605 $isStale = false;
1606
1607 return true;
1608 }
1609
1619 final public function reapCheckKey( $key, $purgeTimestamp, &$isStale = false ) {
1620 $purge = $this->parsePurgeValue( $this->cache->get( self::TIME_KEY_PREFIX . $key ) );
1621 if ( $purge && $purge[self::FLD_TIME] < $purgeTimestamp ) {
1622 $isStale = true;
1623 $this->logger->warning( "Reaping stale check key '$key'." );
1624 $ok = $this->cache->changeTTL( self::TIME_KEY_PREFIX . $key, self::TTL_SECOND );
1625 if ( !$ok ) {
1626 $this->logger->error( "Could not complete reap of check key '$key'." );
1627 }
1628
1629 return $ok;
1630 }
1631
1632 $isStale = false;
1633
1634 return false;
1635 }
1636
1644 public function makeKey( $class, $component = null ) {
1645 return $this->cache->makeKey( ...func_get_args() );
1646 }
1647
1655 public function makeGlobalKey( $class, $component = null ) {
1656 return $this->cache->makeGlobalKey( ...func_get_args() );
1657 }
1658
1665 final public function makeMultiKeys( array $entities, callable $keyFunc ) {
1666 $map = [];
1667 foreach ( $entities as $entity ) {
1668 $map[$keyFunc( $entity, $this )] = $entity;
1669 }
1670
1671 return new ArrayIterator( $map );
1672 }
1673
1678 final public function getLastError() {
1679 if ( $this->lastRelayError ) {
1680 // If the cache and the relayer failed, focus on the latter.
1681 // An update not making it to the relayer means it won't show up
1682 // in other DCs (nor will consistent re-hashing see up-to-date values).
1683 // On the other hand, if just the cache update failed, then it should
1684 // eventually be applied by the relayer.
1685 return $this->lastRelayError;
1686 }
1687
1688 $code = $this->cache->getLastError();
1689 switch ( $code ) {
1690 case BagOStuff::ERR_NONE:
1691 return self::ERR_NONE;
1692 case BagOStuff::ERR_NO_RESPONSE:
1693 return self::ERR_NO_RESPONSE;
1694 case BagOStuff::ERR_UNREACHABLE:
1695 return self::ERR_UNREACHABLE;
1696 default:
1697 return self::ERR_UNEXPECTED;
1698 }
1699 }
1700
1704 final public function clearLastError() {
1705 $this->cache->clearLastError();
1706 $this->lastRelayError = self::ERR_NONE;
1707 }
1708
1714 public function clearProcessCache() {
1715 $this->processCaches = [];
1716 }
1717
1738 final public function useInterimHoldOffCaching( $enabled ) {
1739 $this->useInterimHoldOffCaching = $enabled;
1740 }
1741
1747 public function getQoS( $flag ) {
1748 return $this->cache->getQoS( $flag );
1749 }
1750
1814 public function adaptiveTTL( $mtime, $maxTTL, $minTTL = 30, $factor = 0.2 ) {
1815 if ( is_float( $mtime ) || ctype_digit( $mtime ) ) {
1816 $mtime = (int)$mtime; // handle fractional seconds and string integers
1817 }
1818
1819 if ( !is_int( $mtime ) || $mtime <= 0 ) {
1820 return $minTTL; // no last-modified time provided
1821 }
1822
1823 $age = $this->getCurrentTime() - $mtime;
1824
1825 return (int)min( $maxTTL, max( $minTTL, $factor * $age ) );
1826 }
1827
1832 final public function getWarmupKeyMisses() {
1833 return $this->warmupKeyMisses;
1834 }
1835
1846 protected function relayPurge( $key, $ttl, $holdoff ) {
1847 if ( $this->mcrouterAware ) {
1848 // See https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup
1849 // Wildcards select all matching routes, e.g. the WAN cluster on all DCs
1850 $ok = $this->cache->set(
1851 "/*/{$this->cluster}/{$key}",
1852 $this->makePurgeValue( $this->getCurrentTime(), self::HOLDOFF_NONE ),
1853 $ttl
1854 );
1855 } elseif ( $this->purgeRelayer instanceof EventRelayerNull ) {
1856 // This handles the mcrouter and the single-DC case
1857 $ok = $this->cache->set(
1858 $key,
1859 $this->makePurgeValue( $this->getCurrentTime(), self::HOLDOFF_NONE ),
1860 $ttl
1861 );
1862 } else {
1863 $event = $this->cache->modifySimpleRelayEvent( [
1864 'cmd' => 'set',
1865 'key' => $key,
1866 'val' => 'PURGED:$UNIXTIME$:' . (int)$holdoff,
1867 'ttl' => max( $ttl, self::TTL_SECOND ),
1868 'sbt' => true, // substitute $UNIXTIME$ with actual microtime
1869 ] );
1870
1871 $ok = $this->purgeRelayer->notify( $this->purgeChannel, $event );
1872 if ( !$ok ) {
1873 $this->lastRelayError = self::ERR_RELAY;
1874 }
1875 }
1876
1877 return $ok;
1878 }
1879
1886 protected function relayDelete( $key ) {
1887 if ( $this->mcrouterAware ) {
1888 // See https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup
1889 // Wildcards select all matching routes, e.g. the WAN cluster on all DCs
1890 $ok = $this->cache->delete( "/*/{$this->cluster}/{$key}" );
1891 } elseif ( $this->purgeRelayer instanceof EventRelayerNull ) {
1892 // Some other proxy handles broadcasting or there is only one datacenter
1893 $ok = $this->cache->delete( $key );
1894 } else {
1895 $event = $this->cache->modifySimpleRelayEvent( [
1896 'cmd' => 'delete',
1897 'key' => $key,
1898 ] );
1899
1900 $ok = $this->purgeRelayer->notify( $this->purgeChannel, $event );
1901 if ( !$ok ) {
1902 $this->lastRelayError = self::ERR_RELAY;
1903 }
1904 }
1905
1906 return $ok;
1907 }
1908
1922 protected function isAliveOrInGracePeriod( $curTTL, $graceTTL ) {
1923 if ( $curTTL > 0 ) {
1924 return true;
1925 } elseif ( $graceTTL <= 0 ) {
1926 return false;
1927 }
1928
1929 $ageStale = abs( $curTTL ); // seconds of staleness
1930 $curGTTL = ( $graceTTL - $ageStale ); // current grace-time-to-live
1931 if ( $curGTTL <= 0 ) {
1932 return false; // already out of grace period
1933 }
1934
1935 // Chance of using a stale value is the complement of the chance of refreshing it
1936 return !$this->worthRefreshExpiring( $curGTTL, $graceTTL );
1937 }
1938
1952 protected function worthRefreshExpiring( $curTTL, $lowTTL ) {
1953 if ( $lowTTL <= 0 ) {
1954 return false;
1955 } elseif ( $curTTL >= $lowTTL ) {
1956 return false;
1957 } elseif ( $curTTL <= 0 ) {
1958 return false;
1959 }
1960
1961 $chance = ( 1 - $curTTL / $lowTTL );
1962
1963 return mt_rand( 1, 1e9 ) <= 1e9 * $chance;
1964 }
1965
1981 protected function worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh, $now ) {
1982 if ( $ageNew < 0 || $timeTillRefresh <= 0 ) {
1983 return false;
1984 }
1985
1986 $age = $now - $asOf;
1987 $timeOld = $age - $ageNew;
1988 if ( $timeOld <= 0 ) {
1989 return false;
1990 }
1991
1992 // Lifecycle is: new, ramp-up refresh chance, full refresh chance.
1993 // Note that the "expected # of refreshes" for the ramp-up time range is half of what it
1994 // would be if P(refresh) was at its full value during that time range.
1995 $refreshWindowSec = max( $timeTillRefresh - $ageNew - self::RAMPUP_TTL / 2, 1 );
1996 // P(refresh) * (# hits in $refreshWindowSec) = (expected # of refreshes)
1997 // P(refresh) * ($refreshWindowSec * $popularHitsPerSec) = 1
1998 // P(refresh) = 1/($refreshWindowSec * $popularHitsPerSec)
1999 $chance = 1 / ( self::HIT_RATE_HIGH * $refreshWindowSec );
2000
2001 // Ramp up $chance from 0 to its nominal value over RAMPUP_TTL seconds to avoid stampedes
2002 $chance *= ( $timeOld <= self::RAMPUP_TTL ) ? $timeOld / self::RAMPUP_TTL : 1;
2003
2004 return mt_rand( 1, 1e9 ) <= 1e9 * $chance;
2005 }
2006
2016 protected function isValid( $value, $versioned, $asOf, $minTime ) {
2017 if ( $versioned && !isset( $value[self::VFLD_VERSION] ) ) {
2018 return false;
2019 } elseif ( $minTime > 0 && $asOf < $minTime ) {
2020 return false;
2021 }
2022
2023 return true;
2024 }
2025
2034 protected function wrap( $value, $ttl, $now ) {
2035 return [
2036 self::FLD_VERSION => self::VERSION,
2037 self::FLD_VALUE => $value,
2038 self::FLD_TTL => $ttl,
2039 self::FLD_TIME => $now
2040 ];
2041 }
2042
2050 protected function unwrap( $wrapped, $now ) {
2051 // Check if the value is a tombstone
2052 $purge = $this->parsePurgeValue( $wrapped );
2053 if ( $purge !== false ) {
2054 // Purged values should always have a negative current $ttl
2055 $curTTL = min( $purge[self::FLD_TIME] - $now, self::TINY_NEGATIVE );
2056 return [ false, $curTTL ];
2057 }
2058
2059 if ( !is_array( $wrapped ) // not found
2060 || !isset( $wrapped[self::FLD_VERSION] ) // wrong format
2061 || $wrapped[self::FLD_VERSION] !== self::VERSION // wrong version
2062 ) {
2063 return [ false, null ];
2064 }
2065
2066 $flags = $wrapped[self::FLD_FLAGS] ?? 0;
2067 if ( ( $flags & self::FLG_STALE ) == self::FLG_STALE ) {
2068 // Treat as expired, with the cache time as the expiration
2069 $age = $now - $wrapped[self::FLD_TIME];
2070 $curTTL = min( -$age, self::TINY_NEGATIVE );
2071 } elseif ( $wrapped[self::FLD_TTL] > 0 ) {
2072 // Get the approximate time left on the key
2073 $age = $now - $wrapped[self::FLD_TIME];
2074 $curTTL = max( $wrapped[self::FLD_TTL] - $age, 0.0 );
2075 } else {
2076 // Key had no TTL, so the time left is unbounded
2077 $curTTL = INF;
2078 }
2079
2080 if ( $wrapped[self::FLD_TIME] < $this->epoch ) {
2081 // Values this old are ignored
2082 return [ false, null ];
2083 }
2084
2085 return [ $wrapped[self::FLD_VALUE], $curTTL ];
2086 }
2087
2093 protected static function prefixCacheKeys( array $keys, $prefix ) {
2094 $res = [];
2095 foreach ( $keys as $key ) {
2096 $res[] = $prefix . $key;
2097 }
2098
2099 return $res;
2100 }
2101
2106 protected function determineKeyClass( $key ) {
2107 $parts = explode( ':', $key );
2108
2109 return $parts[1] ?? $parts[0]; // sanity
2110 }
2111
2117 protected function parsePurgeValue( $value ) {
2118 if ( !is_string( $value ) ) {
2119 return false;
2120 }
2121
2122 $segments = explode( ':', $value, 3 );
2123 if ( !isset( $segments[0] ) || !isset( $segments[1] )
2124 || "{$segments[0]}:" !== self::PURGE_VAL_PREFIX
2125 ) {
2126 return false;
2127 }
2128
2129 if ( !isset( $segments[2] ) ) {
2130 // Back-compat with old purge values without holdoff
2131 $segments[2] = self::HOLDOFF_TTL;
2132 }
2133
2134 if ( $segments[1] < $this->epoch ) {
2135 // Values this old are ignored
2136 return false;
2137 }
2138
2139 return [
2140 self::FLD_TIME => (float)$segments[1],
2141 self::FLD_HOLDOFF => (int)$segments[2],
2142 ];
2143 }
2144
2150 protected function makePurgeValue( $timestamp, $holdoff ) {
2151 return self::PURGE_VAL_PREFIX . (float)$timestamp . ':' . (int)$holdoff;
2152 }
2153
2158 protected function getProcessCache( $group ) {
2159 if ( !isset( $this->processCaches[$group] ) ) {
2160 list( , $n ) = explode( ':', $group );
2161 $this->processCaches[$group] = new MapCacheLRU( (int)$n );
2162 }
2163
2164 return $this->processCaches[$group];
2165 }
2166
2173 private function getNonProcessCachedKeys( array $keys, array $opts, $pcTTL ) {
2174 $keysFound = [];
2175 if ( isset( $opts['pcTTL'] ) && $opts['pcTTL'] > 0 && $this->callbackDepth == 0 ) {
2176 $pcGroup = $opts['pcGroup'] ?? self::PC_PRIMARY;
2177 $procCache = $this->getProcessCache( $pcGroup );
2178 foreach ( $keys as $key ) {
2179 if ( $procCache->has( $key, $pcTTL ) ) {
2180 $keysFound[] = $key;
2181 }
2182 }
2183 }
2184
2185 return array_diff( $keys, $keysFound );
2186 }
2187
2193 private function getRawKeysForWarmup( array $keys, array $checkKeys ) {
2194 if ( !$keys ) {
2195 return [];
2196 }
2197
2198 $keysWarmUp = [];
2199 // Get all the value keys to fetch...
2200 foreach ( $keys as $key ) {
2201 $keysWarmUp[] = self::VALUE_KEY_PREFIX . $key;
2202 }
2203 // Get all the check keys to fetch...
2204 foreach ( $checkKeys as $i => $checkKeyOrKeys ) {
2205 if ( is_int( $i ) ) {
2206 // Single check key that applies to all value keys
2207 $keysWarmUp[] = self::TIME_KEY_PREFIX . $checkKeyOrKeys;
2208 } else {
2209 // List of check keys that apply to value key $i
2210 $keysWarmUp = array_merge(
2211 $keysWarmUp,
2212 self::prefixCacheKeys( $checkKeyOrKeys, self::TIME_KEY_PREFIX )
2213 );
2214 }
2215 }
2216
2217 $warmupCache = $this->cache->getMulti( $keysWarmUp );
2218 $warmupCache += array_fill_keys( $keysWarmUp, false );
2219
2220 return $warmupCache;
2221 }
2222
2227 protected function getCurrentTime() {
2228 return $this->wallClockOverride ?: microtime( true );
2229 }
2230
2235 public function setMockTime( &$time ) {
2236 $this->wallClockOverride =& $time;
2237 $this->cache->setMockTime( $time );
2238 }
2239}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Class representing a cache/ephemeral data store.
Definition BagOStuff.php:58
A BagOStuff object with no objects in it.
No-op class for publishing messages into a PubSub system.
Base class for reliable event relays.
Handles a simple LRU key/value map with a maximum number of entries.
Multi-datacenter aware caching interface.
int $callbackDepth
Callback stack depth for getWithSetCallback()
const TINY_NEGATIVE
Tiny negative float to use when CTL comes up >= 0 due to clock skew.
const HOLDOFF_TTL
Seconds to tombstone keys on delete()
const HOT_TTR
The time length of the "popularity" refresh window for hot keys.
__construct(array $params)
unwrap( $wrapped, $now)
Do not use this method outside WANObjectCache.
worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh, $now)
Check if a key is due for randomized regeneration due to its popularity.
touchCheckKey( $key, $holdoff=self::HOLDOFF_TTL)
Purge a "check" key from all datacenters, invalidating keys that use it.
adaptiveTTL( $mtime, $maxTTL, $minTTL=30, $factor=0.2)
Get a TTL that is higher for objects that have not changed recently.
string $cluster
Cache cluster name for mcrouter use.
const GRACE_TTL_NONE
Idiom for set()/getWithSetCallback() for "no post-expired grace period".
int $warmupKeyMisses
Key fetched.
float null $wallClockOverride
mixed[] $warmupCache
Temporary warm-up cache.
const VERSION
Cache format version number.
const TTL_UNCACHEABLE
Idiom for getWithSetCallback() callbacks to avoid calling set()
const LOW_TTL
Default remaining TTL at which to consider pre-emptive regeneration.
relayPurge( $key, $ttl, $holdoff)
Do the actual async bus purge of a key.
getLastError()
Get the "last error" registered; clearLastError() should be called manually.
BagOStuff $cache
The local datacenter cache.
getNonProcessCachedKeys(array $keys, array $opts, $pcTTL)
isValid( $value, $versioned, $asOf, $minTime)
Check whether $value is appropriately versioned and not older than $minTime (if set)
processCheckKeys(array $timeKeys, array $wrappedValues, $now)
doGetWithSetCallback( $key, $ttl, $callback, array $opts, &$asOf=null)
Do the actual I/O for getWithSetCallback() when needed.
const HOLDOFF_NONE
Idiom for delete() for "no hold-off".
getMulti(array $keys, &$curTTLs=[], array $checkKeys=[], array &$asOfs=[])
Fetch the value of several keys from cache.
getCheckKeyTime( $key)
Fetch the value of a timestamp "check" key.
relayDelete( $key)
Do the actual async bus delete of a key.
const LOCK_TTL
Seconds to keep lock keys around.
getMultiWithUnionSetCallback(ArrayIterator $keyedIds, $ttl, callable $callback, array $opts=[])
Method to fetch/regenerate multiple cache keys at once.
LoggerInterface $logger
static prefixCacheKeys(array $keys, $prefix)
getMultiWithSetCallback(ArrayIterator $keyedIds, $ttl, callable $callback, array $opts=[])
Method to fetch multiple cache keys at once with regeneration.
const HIT_RATE_HIGH
Hits/second for a refresh to be expected within the "popularity" window.
EventRelayer $purgeRelayer
Bus that handles purge broadcasts.
const INTERIM_KEY_TTL
Seconds to keep interim value keys for tombstoned keys around.
static newEmpty()
Get an instance that wraps EmptyBagOStuff.
worthRefreshExpiring( $curTTL, $lowTTL)
Check if a key is nearing expiration and thus due for randomized regeneration.
getWithSetCallback( $key, $ttl, $callback, array $opts=[])
Method to fetch/regenerate cache keys.
makeMultiKeys(array $entities, callable $keyFunc)
setInterimValue( $key, $wrapped, $tempTTL)
bool $useInterimHoldOffCaching
Whether to use "interim" caching while keys are tombstoned.
const MAX_READ_LAG
Max replication+snapshot lag before applying TTL_LAGGED or disallowing set()
const CHECK_KEY_TTL
Seconds to keep dependency purge keys around.
const MIN_TIMESTAMP_NONE
Idiom for getWithSetCallback() for "no minimum required as-of timestamp".
useInterimHoldOffCaching( $enabled)
Enable or disable the use of brief caching for tombstoned keys.
StatsdDataFactoryInterface $stats
clearProcessCache()
Clear the in-process caches; useful for testing.
string $region
Physical region for mcrouter use.
wrap( $value, $ttl, $now)
Do not use this method outside WANObjectCache.
int $lastRelayError
ERR_* constant for the "last error" registry.
getInterimValue( $key, $versioned, $minTime, &$asOf)
string $purgeChannel
Purge channel name.
float $epoch
Unix timestamp of the oldest possible valid values.
callable null $asyncHandler
Function that takes a WAN cache callback and runs it later.
reap( $key, $purgeTimestamp, &$isStale=false)
Set a key to soon expire in the local cluster if it pre-dates $purgeTimestamp.
makePurgeValue( $timestamp, $holdoff)
getRawKeysForWarmup(array $keys, array $checkKeys)
setLogger(LoggerInterface $logger)
reapCheckKey( $key, $purgeTimestamp, &$isStale=false)
Set a "check" key to soon expire in the local cluster if it pre-dates $purgeTimestamp.
makeKey( $class, $component=null)
clearLastError()
Clear the "last error" registry.
const STALE_TTL_NONE
Idiom for set()/getWithSetCallback() for "do not augment the storage medium TTL".
MapCacheLRU[] $processCaches
Map of group PHP instance caches.
const TSE_NONE
Idiom for getWithSetCallback() callbacks to 'lockTSE' logic.
resetCheckKey( $key)
Delete a "check" key from all datacenters, invalidating keys that use it.
makeGlobalKey( $class, $component=null)
const MAX_COMMIT_DELAY
Max time expected to pass between delete() and DB commit finishing.
const AGE_NEW
Never consider performing "popularity" refreshes until a key reaches this age.
const RAMPUP_TTL
Seconds to ramp up to the "popularity" refresh chance after a key is no longer new.
const TTL_LAGGED
Max TTL to store keys when a data sourced is lagged.
isAliveOrInGracePeriod( $curTTL, $graceTTL)
Check if a key is fresh or in the grace window and thus due for randomized reuse.
getMultiCheckKeyTime(array $keys)
Fetch the values of each timestamp "check" key.
$mcrouterAware
@bar bool Whether to use mcrouter key prefixing for routing
$res
Definition database.txt:21
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition deferred.txt:11
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition hooks.txt:1841
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImgAuthModifyHeaders':Executed just before a file is streamed to a user via img_auth.php, allowing headers to be modified beforehand. $title:LinkTarget object & $headers:HTTP headers(name=> value, names are case insensitive). Two headers get special handling:If-Modified-Since(value must be a valid HTTP date) and Range(must be of the form "bytes=(\d*-\d*)") will be honored when streaming the file. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUnknownUser':When a user doesn 't exist locally, this hook is called to give extensions an opportunity to auto-create it. If the auto-creation is successful, return false. $name:User name 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED since 1.16! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED since 1.28! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition hooks.txt:2042
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition hooks.txt:181
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition hooks.txt:895
processing should stop and the error should be shown to the user * false
Definition hooks.txt:187
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
Generic base class for storage interfaces.
you have access to all of the normal MediaWiki so you can get a DB use the cache
$cache
Definition mcc.php:33
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
$params