MediaWiki  REL1_31
WANObjectCache.php
Go to the documentation of this file.
1 <?php
22 use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
23 use Psr\Log\LoggerAwareInterface;
24 use Psr\Log\LoggerInterface;
25 use Psr\Log\NullLogger;
26 
87 class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
89  protected $cache;
91  protected $processCaches = [];
93  protected $purgeChannel;
95  protected $purgeRelayer;
97  protected $mcrouterAware;
99  protected $region;
101  protected $cluster;
103  protected $logger;
105  protected $stats;
107  protected $useInterimHoldOffCaching = true;
109  protected $asyncHandler;
110 
113 
115  private $callbackDepth = 0;
117  private $warmupCache = [];
119  private $warmupKeyMisses = 0;
120 
123 
125  const MAX_COMMIT_DELAY = 3;
127  const MAX_READ_LAG = 7;
129  const HOLDOFF_TTL = 11; // MAX_COMMIT_DELAY + MAX_READ_LAG + 1
130 
134  const INTERIM_KEY_TTL = 1;
135 
137  const LOCK_TTL = 10;
139  const LOW_TTL = 30;
140 
142  const AGE_NEW = 60;
144  const HOT_TTR = 900;
146  const HIT_RATE_HIGH = 1;
148  const RAMPUP_TTL = 30;
149 
151  const TTL_UNCACHEABLE = -1;
153  const TSE_NONE = -1;
155  const TTL_LAGGED = 30;
157  const HOLDOFF_NONE = 0;
159  const STALE_TTL_NONE = 0;
161  const GRACE_TTL_NONE = 0;
162 
164  const MIN_TIMESTAMP_NONE = 0.0;
165 
167  const TINY_NEGATIVE = -0.000001;
168 
170  const VERSION = 1;
171 
172  const FLD_VERSION = 0; // key to cache version number
173  const FLD_VALUE = 1; // key to the cached value
174  const FLD_TTL = 2; // key to the original TTL
175  const FLD_TIME = 3; // key to the cache time
176  const FLD_FLAGS = 4; // key to the flags bitfield
177  const FLD_HOLDOFF = 5; // key to any hold-off TTL
178 
180  const FLG_STALE = 1;
181 
182  const ERR_NONE = 0; // no error
183  const ERR_NO_RESPONSE = 1; // no response
184  const ERR_UNREACHABLE = 2; // can't connect
185  const ERR_UNEXPECTED = 3; // response gave some error
186  const ERR_RELAY = 4; // relay broadcast failed
187 
188  const VALUE_KEY_PREFIX = 'WANCache:v:';
189  const INTERIM_KEY_PREFIX = 'WANCache:i:';
190  const TIME_KEY_PREFIX = 'WANCache:t:';
191  const MUTEX_KEY_PREFIX = 'WANCache:m:';
192 
193  const PURGE_VAL_PREFIX = 'PURGED:';
194 
195  const VFLD_DATA = 'WOC:d'; // key to the value of versioned data
196  const VFLD_VERSION = 'WOC:v'; // key to the version of the value present
197 
198  const PC_PRIMARY = 'primary:1000'; // process cache name and max key count
199 
200  const DEFAULT_PURGE_CHANNEL = 'wancache-purge';
201 
227  public function __construct( array $params ) {
228  $this->cache = $params['cache'];
229  $this->purgeChannel = isset( $params['channels']['purge'] )
230  ? $params['channels']['purge']
232  $this->purgeRelayer = isset( $params['relayers']['purge'] )
233  ? $params['relayers']['purge']
234  : new EventRelayerNull( [] );
235  $this->region = isset( $params['region'] ) ? $params['region'] : 'main';
236  $this->cluster = isset( $params['cluster'] ) ? $params['cluster'] : 'wan-main';
237  $this->mcrouterAware = !empty( $params['mcrouterAware'] );
238 
239  $this->setLogger( isset( $params['logger'] ) ? $params['logger'] : new NullLogger() );
240  $this->stats = isset( $params['stats'] ) ? $params['stats'] : new NullStatsdDataFactory();
241  $this->asyncHandler = isset( $params['asyncHandler'] ) ? $params['asyncHandler'] : null;
242  }
243 
247  public function setLogger( LoggerInterface $logger ) {
248  $this->logger = $logger;
249  }
250 
256  public static function newEmpty() {
257  return new static( [
258  'cache' => new EmptyBagOStuff()
259  ] );
260  }
261 
303  final public function get( $key, &$curTTL = null, array $checkKeys = [], &$asOf = null ) {
304  $curTTLs = [];
305  $asOfs = [];
306  $values = $this->getMulti( [ $key ], $curTTLs, $checkKeys, $asOfs );
307  $curTTL = isset( $curTTLs[$key] ) ? $curTTLs[$key] : null;
308  $asOf = isset( $asOfs[$key] ) ? $asOfs[$key] : null;
309 
310  return isset( $values[$key] ) ? $values[$key] : false;
311  }
312 
325  final public function getMulti(
326  array $keys, &$curTTLs = [], array $checkKeys = [], array &$asOfs = []
327  ) {
328  $result = [];
329  $curTTLs = [];
330  $asOfs = [];
331 
332  $vPrefixLen = strlen( self::VALUE_KEY_PREFIX );
333  $valueKeys = self::prefixCacheKeys( $keys, self::VALUE_KEY_PREFIX );
334 
335  $checkKeysForAll = [];
336  $checkKeysByKey = [];
337  $checkKeysFlat = [];
338  foreach ( $checkKeys as $i => $checkKeyGroup ) {
339  $prefixed = self::prefixCacheKeys( (array)$checkKeyGroup, self::TIME_KEY_PREFIX );
340  $checkKeysFlat = array_merge( $checkKeysFlat, $prefixed );
341  // Is this check keys for a specific cache key, or for all keys being fetched?
342  if ( is_int( $i ) ) {
343  $checkKeysForAll = array_merge( $checkKeysForAll, $prefixed );
344  } else {
345  $checkKeysByKey[$i] = isset( $checkKeysByKey[$i] )
346  ? array_merge( $checkKeysByKey[$i], $prefixed )
347  : $prefixed;
348  }
349  }
350 
351  // Fetch all of the raw values
352  $keysGet = array_merge( $valueKeys, $checkKeysFlat );
353  if ( $this->warmupCache ) {
354  $wrappedValues = array_intersect_key( $this->warmupCache, array_flip( $keysGet ) );
355  $keysGet = array_diff( $keysGet, array_keys( $wrappedValues ) ); // keys left to fetch
356  $this->warmupKeyMisses += count( $keysGet );
357  } else {
358  $wrappedValues = [];
359  }
360  if ( $keysGet ) {
361  $wrappedValues += $this->cache->getMulti( $keysGet );
362  }
363  // Time used to compare/init "check" keys (derived after getMulti() to be pessimistic)
364  $now = $this->getCurrentTime();
365 
366  // Collect timestamps from all "check" keys
367  $purgeValuesForAll = $this->processCheckKeys( $checkKeysForAll, $wrappedValues, $now );
368  $purgeValuesByKey = [];
369  foreach ( $checkKeysByKey as $cacheKey => $checks ) {
370  $purgeValuesByKey[$cacheKey] =
371  $this->processCheckKeys( $checks, $wrappedValues, $now );
372  }
373 
374  // Get the main cache value for each key and validate them
375  foreach ( $valueKeys as $vKey ) {
376  if ( !isset( $wrappedValues[$vKey] ) ) {
377  continue; // not found
378  }
379 
380  $key = substr( $vKey, $vPrefixLen ); // unprefix
381 
382  list( $value, $curTTL ) = $this->unwrap( $wrappedValues[$vKey], $now );
383  if ( $value !== false ) {
384  $result[$key] = $value;
385 
386  // Force dependant keys to be invalid for a while after purging
387  // to reduce race conditions involving stale data getting cached
388  $purgeValues = $purgeValuesForAll;
389  if ( isset( $purgeValuesByKey[$key] ) ) {
390  $purgeValues = array_merge( $purgeValues, $purgeValuesByKey[$key] );
391  }
392  foreach ( $purgeValues as $purge ) {
393  $safeTimestamp = $purge[self::FLD_TIME] + $purge[self::FLD_HOLDOFF];
394  if ( $safeTimestamp >= $wrappedValues[$vKey][self::FLD_TIME] ) {
395  // How long ago this value was expired by *this* check key
396  $ago = min( $purge[self::FLD_TIME] - $now, self::TINY_NEGATIVE );
397  // How long ago this value was expired by *any* known check key
398  $curTTL = min( $curTTL, $ago );
399  }
400  }
401  }
402  $curTTLs[$key] = $curTTL;
403  $asOfs[$key] = ( $value !== false ) ? $wrappedValues[$vKey][self::FLD_TIME] : null;
404  }
405 
406  return $result;
407  }
408 
416  private function processCheckKeys( array $timeKeys, array $wrappedValues, $now ) {
417  $purgeValues = [];
418  foreach ( $timeKeys as $timeKey ) {
419  $purge = isset( $wrappedValues[$timeKey] )
420  ? $this->parsePurgeValue( $wrappedValues[$timeKey] )
421  : false;
422  if ( $purge === false ) {
423  // Key is not set or invalid; regenerate
424  $newVal = $this->makePurgeValue( $now, self::HOLDOFF_TTL );
425  $this->cache->add( $timeKey, $newVal, self::CHECK_KEY_TTL );
426  $purge = $this->parsePurgeValue( $newVal );
427  }
428  $purgeValues[] = $purge;
429  }
430  return $purgeValues;
431  }
432 
498  final public function set( $key, $value, $ttl = 0, array $opts = [] ) {
499  $now = $this->getCurrentTime();
500  $lockTSE = isset( $opts['lockTSE'] ) ? $opts['lockTSE'] : self::TSE_NONE;
501  $staleTTL = isset( $opts['staleTTL'] ) ? $opts['staleTTL'] : self::STALE_TTL_NONE;
502  $age = isset( $opts['since'] ) ? max( 0, $now - $opts['since'] ) : 0;
503  $lag = isset( $opts['lag'] ) ? $opts['lag'] : 0;
504 
505  // Do not cache potentially uncommitted data as it might get rolled back
506  if ( !empty( $opts['pending'] ) ) {
507  $this->logger->info( 'Rejected set() for {cachekey} due to pending writes.',
508  [ 'cachekey' => $key ] );
509 
510  return true; // no-op the write for being unsafe
511  }
512 
513  $wrapExtra = []; // additional wrapped value fields
514  // Check if there's a risk of writing stale data after the purge tombstone expired
515  if ( $lag === false || ( $lag + $age ) > self::MAX_READ_LAG ) {
516  // Case A: read lag with "lockTSE"; save but record value as stale
517  if ( $lockTSE >= 0 ) {
518  $ttl = max( 1, (int)$lockTSE ); // set() expects seconds
519  $wrapExtra[self::FLD_FLAGS] = self::FLG_STALE; // mark as stale
520  // Case B: any long-running transaction; ignore this set()
521  } elseif ( $age > self::MAX_READ_LAG ) {
522  $this->logger->info( 'Rejected set() for {cachekey} due to snapshot lag.',
523  [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ] );
524 
525  return true; // no-op the write for being unsafe
526  // Case C: high replication lag; lower TTL instead of ignoring all set()s
527  } elseif ( $lag === false || $lag > self::MAX_READ_LAG ) {
528  $ttl = $ttl ? min( $ttl, self::TTL_LAGGED ) : self::TTL_LAGGED;
529  $this->logger->warning( 'Lowered set() TTL for {cachekey} due to replication lag.',
530  [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ] );
531  // Case D: medium length request with medium replication lag; ignore this set()
532  } else {
533  $this->logger->info( 'Rejected set() for {cachekey} due to high read lag.',
534  [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ] );
535 
536  return true; // no-op the write for being unsafe
537  }
538  }
539 
540  // Wrap that value with time/TTL/version metadata
541  $wrapped = $this->wrap( $value, $ttl, $now ) + $wrapExtra;
542 
543  $func = function ( $cache, $key, $cWrapped ) use ( $wrapped ) {
544  return ( is_string( $cWrapped ) )
545  ? false // key is tombstoned; do nothing
546  : $wrapped;
547  };
548 
549  return $this->cache->merge( self::VALUE_KEY_PREFIX . $key, $func, $ttl + $staleTTL, 1 );
550  }
551 
613  final public function delete( $key, $ttl = self::HOLDOFF_TTL ) {
614  $key = self::VALUE_KEY_PREFIX . $key;
615 
616  if ( $ttl <= 0 ) {
617  // Publish the purge to all datacenters
618  $ok = $this->relayDelete( $key );
619  } else {
620  // Publish the purge to all datacenters
621  $ok = $this->relayPurge( $key, $ttl, self::HOLDOFF_NONE );
622  }
623 
624  return $ok;
625  }
626 
646  final public function getCheckKeyTime( $key ) {
647  return $this->getMultiCheckKeyTime( [ $key ] )[$key];
648  }
649 
711  final public function getMultiCheckKeyTime( array $keys ) {
712  $rawKeys = [];
713  foreach ( $keys as $key ) {
714  $rawKeys[$key] = self::TIME_KEY_PREFIX . $key;
715  }
716 
717  $rawValues = $this->cache->getMulti( $rawKeys );
718  $rawValues += array_fill_keys( $rawKeys, false );
719 
720  $times = [];
721  foreach ( $rawKeys as $key => $rawKey ) {
722  $purge = $this->parsePurgeValue( $rawValues[$rawKey] );
723  if ( $purge !== false ) {
724  $time = $purge[self::FLD_TIME];
725  } else {
726  // Casting assures identical floats for the next getCheckKeyTime() calls
727  $now = (string)$this->getCurrentTime();
728  $this->cache->add(
729  $rawKey,
730  $this->makePurgeValue( $now, self::HOLDOFF_TTL ),
732  );
733  $time = (float)$now;
734  }
735 
736  $times[$key] = $time;
737  }
738 
739  return $times;
740  }
741 
776  final public function touchCheckKey( $key, $holdoff = self::HOLDOFF_TTL ) {
777  // Publish the purge to all datacenters
778  return $this->relayPurge( self::TIME_KEY_PREFIX . $key, self::CHECK_KEY_TTL, $holdoff );
779  }
780 
808  final public function resetCheckKey( $key ) {
809  // Publish the purge to all datacenters
810  return $this->relayDelete( self::TIME_KEY_PREFIX . $key );
811  }
812 
1057  final public function getWithSetCallback( $key, $ttl, $callback, array $opts = [] ) {
1058  $pcTTL = isset( $opts['pcTTL'] ) ? $opts['pcTTL'] : self::TTL_UNCACHEABLE;
1059 
1060  // Try the process cache if enabled and the cache callback is not within a cache callback.
1061  // Process cache use in nested callbacks is not lag-safe with regard to HOLDOFF_TTL since
1062  // the in-memory value is further lagged than the shared one since it uses a blind TTL.
1063  if ( $pcTTL >= 0 && $this->callbackDepth == 0 ) {
1064  $group = isset( $opts['pcGroup'] ) ? $opts['pcGroup'] : self::PC_PRIMARY;
1065  $procCache = $this->getProcessCache( $group );
1066  $value = $procCache->get( $key );
1067  } else {
1068  $procCache = false;
1069  $value = false;
1070  }
1071 
1072  if ( $value === false ) {
1073  // Fetch the value over the network
1074  if ( isset( $opts['version'] ) ) {
1075  $version = $opts['version'];
1076  $asOf = null;
1077  $cur = $this->doGetWithSetCallback(
1078  $key,
1079  $ttl,
1080  function ( $oldValue, &$ttl, &$setOpts, $oldAsOf )
1081  use ( $callback, $version ) {
1082  if ( is_array( $oldValue )
1083  && array_key_exists( self::VFLD_DATA, $oldValue )
1084  && array_key_exists( self::VFLD_VERSION, $oldValue )
1085  && $oldValue[self::VFLD_VERSION] === $version
1086  ) {
1087  $oldData = $oldValue[self::VFLD_DATA];
1088  } else {
1089  // VFLD_DATA is not set if an old, unversioned, key is present
1090  $oldData = false;
1091  $oldAsOf = null;
1092  }
1093 
1094  return [
1095  self::VFLD_DATA => $callback( $oldData, $ttl, $setOpts, $oldAsOf ),
1096  self::VFLD_VERSION => $version
1097  ];
1098  },
1099  $opts,
1100  $asOf
1101  );
1102  if ( $cur[self::VFLD_VERSION] === $version ) {
1103  // Value created or existed before with version; use it
1104  $value = $cur[self::VFLD_DATA];
1105  } else {
1106  // Value existed before with a different version; use variant key.
1107  // Reflect purges to $key by requiring that this key value be newer.
1108  $value = $this->doGetWithSetCallback(
1109  $this->makeGlobalKey( 'WANCache-key-variant', md5( $key ), $version ),
1110  $ttl,
1111  $callback,
1112  // Regenerate value if not newer than $key
1113  [ 'version' => null, 'minAsOf' => $asOf ] + $opts
1114  );
1115  }
1116  } else {
1117  $value = $this->doGetWithSetCallback( $key, $ttl, $callback, $opts );
1118  }
1119 
1120  // Update the process cache if enabled
1121  if ( $procCache && $value !== false ) {
1122  $procCache->set( $key, $value, $pcTTL );
1123  }
1124  }
1125 
1126  return $value;
1127  }
1128 
1142  protected function doGetWithSetCallback( $key, $ttl, $callback, array $opts, &$asOf = null ) {
1143  $lowTTL = isset( $opts['lowTTL'] ) ? $opts['lowTTL'] : min( self::LOW_TTL, $ttl );
1144  $lockTSE = isset( $opts['lockTSE'] ) ? $opts['lockTSE'] : self::TSE_NONE;
1145  $staleTTL = isset( $opts['staleTTL'] ) ? $opts['staleTTL'] : self::STALE_TTL_NONE;
1146  $graceTTL = isset( $opts['graceTTL'] ) ? $opts['graceTTL'] : self::GRACE_TTL_NONE;
1147  $checkKeys = isset( $opts['checkKeys'] ) ? $opts['checkKeys'] : [];
1148  $busyValue = isset( $opts['busyValue'] ) ? $opts['busyValue'] : null;
1149  $popWindow = isset( $opts['hotTTR'] ) ? $opts['hotTTR'] : self::HOT_TTR;
1150  $ageNew = isset( $opts['ageNew'] ) ? $opts['ageNew'] : self::AGE_NEW;
1151  $minTime = isset( $opts['minAsOf'] ) ? $opts['minAsOf'] : self::MIN_TIMESTAMP_NONE;
1152  $versioned = isset( $opts['version'] );
1153 
1154  // Get a collection name to describe this class of key
1155  $kClass = $this->determineKeyClass( $key );
1156 
1157  // Get the current key value
1158  $curTTL = null;
1159  $cValue = $this->get( $key, $curTTL, $checkKeys, $asOf ); // current value
1160  $value = $cValue; // return value
1161 
1162  $preCallbackTime = $this->getCurrentTime();
1163  // Determine if a cached value regeneration is needed or desired
1164  if ( $value !== false
1165  && $this->isAliveOrInGracePeriod( $curTTL, $graceTTL )
1166  && $this->isValid( $value, $versioned, $asOf, $minTime )
1167  ) {
1168  $preemptiveRefresh = (
1169  $this->worthRefreshExpiring( $curTTL, $lowTTL ) ||
1170  $this->worthRefreshPopular( $asOf, $ageNew, $popWindow, $preCallbackTime )
1171  );
1172 
1173  if ( !$preemptiveRefresh ) {
1174  $this->stats->increment( "wanobjectcache.$kClass.hit.good" );
1175 
1176  return $value;
1177  } elseif ( $this->asyncHandler ) {
1178  // Update the cache value later, such during post-send of an HTTP request
1179  $func = $this->asyncHandler;
1180  $func( function () use ( $key, $ttl, $callback, $opts, $asOf ) {
1181  $opts['minAsOf'] = INF; // force a refresh
1182  $this->doGetWithSetCallback( $key, $ttl, $callback, $opts, $asOf );
1183  } );
1184  $this->stats->increment( "wanobjectcache.$kClass.hit.refresh" );
1185 
1186  return $value;
1187  }
1188  }
1189 
1190  // A deleted key with a negative TTL left must be tombstoned
1191  $isTombstone = ( $curTTL !== null && $value === false );
1192  if ( $isTombstone && $lockTSE <= 0 ) {
1193  // Use the INTERIM value for tombstoned keys to reduce regeneration load
1194  $lockTSE = self::INTERIM_KEY_TTL;
1195  }
1196  // Assume a key is hot if requested soon after invalidation
1197  $isHot = ( $curTTL !== null && $curTTL <= 0 && abs( $curTTL ) <= $lockTSE );
1198  // Use the mutex if there is no value and a busy fallback is given
1199  $checkBusy = ( $busyValue !== null && $value === false );
1200  // Decide whether a single thread should handle regenerations.
1201  // This avoids stampedes when $checkKeys are bumped and when preemptive
1202  // renegerations take too long. It also reduces regenerations while $key
1203  // is tombstoned. This balances cache freshness with avoiding DB load.
1204  $useMutex = ( $isHot || ( $isTombstone && $lockTSE > 0 ) || $checkBusy );
1205 
1206  $lockAcquired = false;
1207  if ( $useMutex ) {
1208  // Acquire a datacenter-local non-blocking lock
1209  if ( $this->cache->add( self::MUTEX_KEY_PREFIX . $key, 1, self::LOCK_TTL ) ) {
1210  // Lock acquired; this thread should update the key
1211  $lockAcquired = true;
1212  } elseif ( $value !== false && $this->isValid( $value, $versioned, $asOf, $minTime ) ) {
1213  $this->stats->increment( "wanobjectcache.$kClass.hit.stale" );
1214  // If it cannot be acquired; then the stale value can be used
1215  return $value;
1216  } else {
1217  // Use the INTERIM value for tombstoned keys to reduce regeneration load.
1218  // For hot keys, either another thread has the lock or the lock failed;
1219  // use the INTERIM value from the last thread that regenerated it.
1220  $value = $this->getInterimValue( $key, $versioned, $minTime, $asOf );
1221  if ( $value !== false ) {
1222  $this->stats->increment( "wanobjectcache.$kClass.hit.volatile" );
1223 
1224  return $value;
1225  }
1226  // Use the busy fallback value if nothing else
1227  if ( $busyValue !== null ) {
1228  $this->stats->increment( "wanobjectcache.$kClass.miss.busy" );
1229 
1230  return is_callable( $busyValue ) ? $busyValue() : $busyValue;
1231  }
1232  }
1233  }
1234 
1235  if ( !is_callable( $callback ) ) {
1236  throw new InvalidArgumentException( "Invalid cache miss callback provided." );
1237  }
1238 
1239  // Generate the new value from the callback...
1240  $setOpts = [];
1242  try {
1243  $value = call_user_func_array( $callback, [ $cValue, &$ttl, &$setOpts, $asOf ] );
1244  } finally {
1246  }
1247  $valueIsCacheable = ( $value !== false && $ttl >= 0 );
1248 
1249  // When delete() is called, writes are write-holed by the tombstone,
1250  // so use a special INTERIM key to pass the new value around threads.
1251  if ( ( $isTombstone && $lockTSE > 0 ) && $valueIsCacheable ) {
1252  $tempTTL = max( 1, (int)$lockTSE ); // set() expects seconds
1253  $newAsOf = $this->getCurrentTime();
1254  $wrapped = $this->wrap( $value, $tempTTL, $newAsOf );
1255  // Avoid using set() to avoid pointless mcrouter broadcasting
1256  $this->setInterimValue( $key, $wrapped, $tempTTL );
1257  }
1258 
1259  if ( $valueIsCacheable ) {
1260  $setOpts['lockTSE'] = $lockTSE;
1261  $setOpts['staleTTL'] = $staleTTL;
1262  // Use best known "since" timestamp if not provided
1263  $setOpts += [ 'since' => $preCallbackTime ];
1264  // Update the cache; this will fail if the key is tombstoned
1265  $this->set( $key, $value, $ttl, $setOpts );
1266  }
1267 
1268  if ( $lockAcquired ) {
1269  // Avoid using delete() to avoid pointless mcrouter broadcasting
1270  $this->cache->changeTTL( self::MUTEX_KEY_PREFIX . $key, (int)$preCallbackTime - 60 );
1271  }
1272 
1273  $this->stats->increment( "wanobjectcache.$kClass.miss.compute" );
1274 
1275  return $value;
1276  }
1277 
1285  protected function getInterimValue( $key, $versioned, $minTime, &$asOf ) {
1286  if ( !$this->useInterimHoldOffCaching ) {
1287  return false; // disabled
1288  }
1289 
1290  $wrapped = $this->cache->get( self::INTERIM_KEY_PREFIX . $key );
1291  list( $value ) = $this->unwrap( $wrapped, $this->getCurrentTime() );
1292  if ( $value !== false && $this->isValid( $value, $versioned, $asOf, $minTime ) ) {
1293  $asOf = $wrapped[self::FLD_TIME];
1294 
1295  return $value;
1296  }
1297 
1298  return false;
1299  }
1300 
1306  protected function setInterimValue( $key, $wrapped, $tempTTL ) {
1307  $this->cache->merge(
1308  self::INTERIM_KEY_PREFIX . $key,
1309  function () use ( $wrapped ) {
1310  return $wrapped;
1311  },
1312  $tempTTL,
1313  1
1314  );
1315  }
1316 
1383  final public function getMultiWithSetCallback(
1384  ArrayIterator $keyedIds, $ttl, callable $callback, array $opts = []
1385  ) {
1386  $valueKeys = array_keys( $keyedIds->getArrayCopy() );
1387  $checkKeys = isset( $opts['checkKeys'] ) ? $opts['checkKeys'] : [];
1388 
1389  // Load required keys into process cache in one go
1390  $this->warmupCache = $this->getRawKeysForWarmup(
1391  $this->getNonProcessCachedKeys( $valueKeys, $opts ),
1392  $checkKeys
1393  );
1394  $this->warmupKeyMisses = 0;
1395 
1396  // Wrap $callback to match the getWithSetCallback() format while passing $id to $callback
1397  $id = null; // current entity ID
1398  $func = function ( $oldValue, &$ttl, &$setOpts, $oldAsOf ) use ( $callback, &$id ) {
1399  return $callback( $id, $oldValue, $ttl, $setOpts, $oldAsOf );
1400  };
1401 
1402  $values = [];
1403  foreach ( $keyedIds as $key => $id ) { // preserve order
1404  $values[$key] = $this->getWithSetCallback( $key, $ttl, $func, $opts );
1405  }
1406 
1407  $this->warmupCache = [];
1408 
1409  return $values;
1410  }
1411 
1477  final public function getMultiWithUnionSetCallback(
1478  ArrayIterator $keyedIds, $ttl, callable $callback, array $opts = []
1479  ) {
1480  $idsByValueKey = $keyedIds->getArrayCopy();
1481  $valueKeys = array_keys( $idsByValueKey );
1482  $checkKeys = isset( $opts['checkKeys'] ) ? $opts['checkKeys'] : [];
1483  unset( $opts['lockTSE'] ); // incompatible
1484  unset( $opts['busyValue'] ); // incompatible
1485 
1486  // Load required keys into process cache in one go
1487  $keysGet = $this->getNonProcessCachedKeys( $valueKeys, $opts );
1488  $this->warmupCache = $this->getRawKeysForWarmup( $keysGet, $checkKeys );
1489  $this->warmupKeyMisses = 0;
1490 
1491  // IDs of entities known to be in need of regeneration
1492  $idsRegen = [];
1493 
1494  // Find out which keys are missing/deleted/stale
1495  $curTTLs = [];
1496  $asOfs = [];
1497  $curByKey = $this->getMulti( $keysGet, $curTTLs, $checkKeys, $asOfs );
1498  foreach ( $keysGet as $key ) {
1499  if ( !array_key_exists( $key, $curByKey ) || $curTTLs[$key] < 0 ) {
1500  $idsRegen[] = $idsByValueKey[$key];
1501  }
1502  }
1503 
1504  // Run the callback to populate the regeneration value map for all required IDs
1505  $newSetOpts = [];
1506  $newTTLsById = array_fill_keys( $idsRegen, $ttl );
1507  $newValsById = $idsRegen ? $callback( $idsRegen, $newTTLsById, $newSetOpts ) : [];
1508 
1509  // Wrap $callback to match the getWithSetCallback() format while passing $id to $callback
1510  $id = null; // current entity ID
1511  $func = function ( $oldValue, &$ttl, &$setOpts, $oldAsOf )
1512  use ( $callback, &$id, $newValsById, $newTTLsById, $newSetOpts )
1513  {
1514  if ( array_key_exists( $id, $newValsById ) ) {
1515  // Value was already regerated as expected, so use the value in $newValsById
1516  $newValue = $newValsById[$id];
1517  $ttl = $newTTLsById[$id];
1518  $setOpts = $newSetOpts;
1519  } else {
1520  // Pre-emptive/popularity refresh and version mismatch cases are not detected
1521  // above and thus $newValsById has no entry. Run $callback on this single entity.
1522  $ttls = [ $id => $ttl ];
1523  $newValue = $callback( [ $id ], $ttls, $setOpts )[$id];
1524  $ttl = $ttls[$id];
1525  }
1526 
1527  return $newValue;
1528  };
1529 
1530  // Run the cache-aside logic using warmupCache instead of persistent cache queries
1531  $values = [];
1532  foreach ( $idsByValueKey as $key => $id ) { // preserve order
1533  $values[$key] = $this->getWithSetCallback( $key, $ttl, $func, $opts );
1534  }
1535 
1536  $this->warmupCache = [];
1537 
1538  return $values;
1539  }
1540 
1553  final public function reap( $key, $purgeTimestamp, &$isStale = false ) {
1554  $minAsOf = $purgeTimestamp + self::HOLDOFF_TTL;
1555  $wrapped = $this->cache->get( self::VALUE_KEY_PREFIX . $key );
1556  if ( is_array( $wrapped ) && $wrapped[self::FLD_TIME] < $minAsOf ) {
1557  $isStale = true;
1558  $this->logger->warning( "Reaping stale value key '$key'." );
1559  $ttlReap = self::HOLDOFF_TTL; // avoids races with tombstone creation
1560  $ok = $this->cache->changeTTL( self::VALUE_KEY_PREFIX . $key, $ttlReap );
1561  if ( !$ok ) {
1562  $this->logger->error( "Could not complete reap of key '$key'." );
1563  }
1564 
1565  return $ok;
1566  }
1567 
1568  $isStale = false;
1569 
1570  return true;
1571  }
1572 
1582  final public function reapCheckKey( $key, $purgeTimestamp, &$isStale = false ) {
1583  $purge = $this->parsePurgeValue( $this->cache->get( self::TIME_KEY_PREFIX . $key ) );
1584  if ( $purge && $purge[self::FLD_TIME] < $purgeTimestamp ) {
1585  $isStale = true;
1586  $this->logger->warning( "Reaping stale check key '$key'." );
1587  $ok = $this->cache->changeTTL( self::TIME_KEY_PREFIX . $key, self::TTL_SECOND );
1588  if ( !$ok ) {
1589  $this->logger->error( "Could not complete reap of check key '$key'." );
1590  }
1591 
1592  return $ok;
1593  }
1594 
1595  $isStale = false;
1596 
1597  return false;
1598  }
1599 
1607  public function makeKey( $class, $component = null ) {
1608  return call_user_func_array( [ $this->cache, __FUNCTION__ ], func_get_args() );
1609  }
1610 
1618  public function makeGlobalKey( $class, $component = null ) {
1619  return call_user_func_array( [ $this->cache, __FUNCTION__ ], func_get_args() );
1620  }
1621 
1628  final public function makeMultiKeys( array $entities, callable $keyFunc ) {
1629  $map = [];
1630  foreach ( $entities as $entity ) {
1631  $map[$keyFunc( $entity, $this )] = $entity;
1632  }
1633 
1634  return new ArrayIterator( $map );
1635  }
1636 
1641  final public function getLastError() {
1642  if ( $this->lastRelayError ) {
1643  // If the cache and the relayer failed, focus on the latter.
1644  // An update not making it to the relayer means it won't show up
1645  // in other DCs (nor will consistent re-hashing see up-to-date values).
1646  // On the other hand, if just the cache update failed, then it should
1647  // eventually be applied by the relayer.
1648  return $this->lastRelayError;
1649  }
1650 
1651  $code = $this->cache->getLastError();
1652  switch ( $code ) {
1653  case BagOStuff::ERR_NONE:
1654  return self::ERR_NONE;
1656  return self::ERR_NO_RESPONSE;
1658  return self::ERR_UNREACHABLE;
1659  default:
1660  return self::ERR_UNEXPECTED;
1661  }
1662  }
1663 
1667  final public function clearLastError() {
1668  $this->cache->clearLastError();
1669  $this->lastRelayError = self::ERR_NONE;
1670  }
1671 
1677  public function clearProcessCache() {
1678  $this->processCaches = [];
1679  }
1680 
1701  final public function useInterimHoldOffCaching( $enabled ) {
1702  $this->useInterimHoldOffCaching = $enabled;
1703  }
1704 
1710  public function getQoS( $flag ) {
1711  return $this->cache->getQoS( $flag );
1712  }
1713 
1777  public function adaptiveTTL( $mtime, $maxTTL, $minTTL = 30, $factor = 0.2 ) {
1778  if ( is_float( $mtime ) || ctype_digit( $mtime ) ) {
1779  $mtime = (int)$mtime; // handle fractional seconds and string integers
1780  }
1781 
1782  if ( !is_int( $mtime ) || $mtime <= 0 ) {
1783  return $minTTL; // no last-modified time provided
1784  }
1785 
1786  $age = $this->getCurrentTime() - $mtime;
1787 
1788  return (int)min( $maxTTL, max( $minTTL, $factor * $age ) );
1789  }
1790 
1795  final public function getWarmupKeyMisses() {
1796  return $this->warmupKeyMisses;
1797  }
1798 
1809  protected function relayPurge( $key, $ttl, $holdoff ) {
1810  if ( $this->mcrouterAware ) {
1811  // See https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup
1812  // Wildcards select all matching routes, e.g. the WAN cluster on all DCs
1813  $ok = $this->cache->set(
1814  "/*/{$this->cluster}/{$key}",
1815  $this->makePurgeValue( $this->getCurrentTime(), self::HOLDOFF_NONE ),
1816  $ttl
1817  );
1818  } elseif ( $this->purgeRelayer instanceof EventRelayerNull ) {
1819  // This handles the mcrouter and the single-DC case
1820  $ok = $this->cache->set(
1821  $key,
1822  $this->makePurgeValue( $this->getCurrentTime(), self::HOLDOFF_NONE ),
1823  $ttl
1824  );
1825  } else {
1826  $event = $this->cache->modifySimpleRelayEvent( [
1827  'cmd' => 'set',
1828  'key' => $key,
1829  'val' => 'PURGED:$UNIXTIME$:' . (int)$holdoff,
1830  'ttl' => max( $ttl, self::TTL_SECOND ),
1831  'sbt' => true, // substitute $UNIXTIME$ with actual microtime
1832  ] );
1833 
1834  $ok = $this->purgeRelayer->notify( $this->purgeChannel, $event );
1835  if ( !$ok ) {
1836  $this->lastRelayError = self::ERR_RELAY;
1837  }
1838  }
1839 
1840  return $ok;
1841  }
1842 
1849  protected function relayDelete( $key ) {
1850  if ( $this->mcrouterAware ) {
1851  // See https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup
1852  // Wildcards select all matching routes, e.g. the WAN cluster on all DCs
1853  $ok = $this->cache->delete( "/*/{$this->cluster}/{$key}" );
1854  } elseif ( $this->purgeRelayer instanceof EventRelayerNull ) {
1855  // Some other proxy handles broadcasting or there is only one datacenter
1856  $ok = $this->cache->delete( $key );
1857  } else {
1858  $event = $this->cache->modifySimpleRelayEvent( [
1859  'cmd' => 'delete',
1860  'key' => $key,
1861  ] );
1862 
1863  $ok = $this->purgeRelayer->notify( $this->purgeChannel, $event );
1864  if ( !$ok ) {
1865  $this->lastRelayError = self::ERR_RELAY;
1866  }
1867  }
1868 
1869  return $ok;
1870  }
1871 
1885  protected function isAliveOrInGracePeriod( $curTTL, $graceTTL ) {
1886  if ( $curTTL > 0 ) {
1887  return true;
1888  } elseif ( $graceTTL <= 0 ) {
1889  return false;
1890  }
1891 
1892  $ageStale = abs( $curTTL ); // seconds of staleness
1893  $curGTTL = ( $graceTTL - $ageStale ); // current grace-time-to-live
1894  if ( $curGTTL <= 0 ) {
1895  return false; // already out of grace period
1896  }
1897 
1898  // Chance of using a stale value is the complement of the chance of refreshing it
1899  return !$this->worthRefreshExpiring( $curGTTL, $graceTTL );
1900  }
1901 
1915  protected function worthRefreshExpiring( $curTTL, $lowTTL ) {
1916  if ( $lowTTL <= 0 ) {
1917  return false;
1918  } elseif ( $curTTL >= $lowTTL ) {
1919  return false;
1920  } elseif ( $curTTL <= 0 ) {
1921  return false;
1922  }
1923 
1924  $chance = ( 1 - $curTTL / $lowTTL );
1925 
1926  return mt_rand( 1, 1e9 ) <= 1e9 * $chance;
1927  }
1928 
1944  protected function worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh, $now ) {
1945  if ( $ageNew < 0 || $timeTillRefresh <= 0 ) {
1946  return false;
1947  }
1948 
1949  $age = $now - $asOf;
1950  $timeOld = $age - $ageNew;
1951  if ( $timeOld <= 0 ) {
1952  return false;
1953  }
1954 
1955  // Lifecycle is: new, ramp-up refresh chance, full refresh chance.
1956  // Note that the "expected # of refreshes" for the ramp-up time range is half of what it
1957  // would be if P(refresh) was at its full value during that time range.
1958  $refreshWindowSec = max( $timeTillRefresh - $ageNew - self::RAMPUP_TTL / 2, 1 );
1959  // P(refresh) * (# hits in $refreshWindowSec) = (expected # of refreshes)
1960  // P(refresh) * ($refreshWindowSec * $popularHitsPerSec) = 1
1961  // P(refresh) = 1/($refreshWindowSec * $popularHitsPerSec)
1962  $chance = 1 / ( self::HIT_RATE_HIGH * $refreshWindowSec );
1963 
1964  // Ramp up $chance from 0 to its nominal value over RAMPUP_TTL seconds to avoid stampedes
1965  $chance *= ( $timeOld <= self::RAMPUP_TTL ) ? $timeOld / self::RAMPUP_TTL : 1;
1966 
1967  return mt_rand( 1, 1e9 ) <= 1e9 * $chance;
1968  }
1969 
1979  protected function isValid( $value, $versioned, $asOf, $minTime ) {
1980  if ( $versioned && !isset( $value[self::VFLD_VERSION] ) ) {
1981  return false;
1982  } elseif ( $minTime > 0 && $asOf < $minTime ) {
1983  return false;
1984  }
1985 
1986  return true;
1987  }
1988 
1997  protected function wrap( $value, $ttl, $now ) {
1998  return [
1999  self::FLD_VERSION => self::VERSION,
2000  self::FLD_VALUE => $value,
2001  self::FLD_TTL => $ttl,
2002  self::FLD_TIME => $now
2003  ];
2004  }
2005 
2013  protected function unwrap( $wrapped, $now ) {
2014  // Check if the value is a tombstone
2015  $purge = $this->parsePurgeValue( $wrapped );
2016  if ( $purge !== false ) {
2017  // Purged values should always have a negative current $ttl
2018  $curTTL = min( $purge[self::FLD_TIME] - $now, self::TINY_NEGATIVE );
2019  return [ false, $curTTL ];
2020  }
2021 
2022  if ( !is_array( $wrapped ) // not found
2023  || !isset( $wrapped[self::FLD_VERSION] ) // wrong format
2024  || $wrapped[self::FLD_VERSION] !== self::VERSION // wrong version
2025  ) {
2026  return [ false, null ];
2027  }
2028 
2029  $flags = isset( $wrapped[self::FLD_FLAGS] ) ? $wrapped[self::FLD_FLAGS] : 0;
2030  if ( ( $flags & self::FLG_STALE ) == self::FLG_STALE ) {
2031  // Treat as expired, with the cache time as the expiration
2032  $age = $now - $wrapped[self::FLD_TIME];
2033  $curTTL = min( -$age, self::TINY_NEGATIVE );
2034  } elseif ( $wrapped[self::FLD_TTL] > 0 ) {
2035  // Get the approximate time left on the key
2036  $age = $now - $wrapped[self::FLD_TIME];
2037  $curTTL = max( $wrapped[self::FLD_TTL] - $age, 0.0 );
2038  } else {
2039  // Key had no TTL, so the time left is unbounded
2040  $curTTL = INF;
2041  }
2042 
2043  return [ $wrapped[self::FLD_VALUE], $curTTL ];
2044  }
2045 
2051  protected static function prefixCacheKeys( array $keys, $prefix ) {
2052  $res = [];
2053  foreach ( $keys as $key ) {
2054  $res[] = $prefix . $key;
2055  }
2056 
2057  return $res;
2058  }
2059 
2064  protected function determineKeyClass( $key ) {
2065  $parts = explode( ':', $key );
2066 
2067  return isset( $parts[1] ) ? $parts[1] : $parts[0]; // sanity
2068  }
2069 
2075  protected function parsePurgeValue( $value ) {
2076  if ( !is_string( $value ) ) {
2077  return false;
2078  }
2079  $segments = explode( ':', $value, 3 );
2080  if ( !isset( $segments[0] ) || !isset( $segments[1] )
2081  || "{$segments[0]}:" !== self::PURGE_VAL_PREFIX
2082  ) {
2083  return false;
2084  }
2085  if ( !isset( $segments[2] ) ) {
2086  // Back-compat with old purge values without holdoff
2087  $segments[2] = self::HOLDOFF_TTL;
2088  }
2089  return [
2090  self::FLD_TIME => (float)$segments[1],
2091  self::FLD_HOLDOFF => (int)$segments[2],
2092  ];
2093  }
2094 
2100  protected function makePurgeValue( $timestamp, $holdoff ) {
2101  return self::PURGE_VAL_PREFIX . (float)$timestamp . ':' . (int)$holdoff;
2102  }
2103 
2108  protected function getProcessCache( $group ) {
2109  if ( !isset( $this->processCaches[$group] ) ) {
2110  list( , $n ) = explode( ':', $group );
2111  $this->processCaches[$group] = new HashBagOStuff( [ 'maxKeys' => (int)$n ] );
2112  }
2113 
2114  return $this->processCaches[$group];
2115  }
2116 
2122  private function getNonProcessCachedKeys( array $keys, array $opts ) {
2123  $keysFound = [];
2124  if ( isset( $opts['pcTTL'] ) && $opts['pcTTL'] > 0 && $this->callbackDepth == 0 ) {
2125  $pcGroup = isset( $opts['pcGroup'] ) ? $opts['pcGroup'] : self::PC_PRIMARY;
2126  $procCache = $this->getProcessCache( $pcGroup );
2127  foreach ( $keys as $key ) {
2128  if ( $procCache->get( $key ) !== false ) {
2129  $keysFound[] = $key;
2130  }
2131  }
2132  }
2133 
2134  return array_diff( $keys, $keysFound );
2135  }
2136 
2142  private function getRawKeysForWarmup( array $keys, array $checkKeys ) {
2143  if ( !$keys ) {
2144  return [];
2145  }
2146 
2147  $keysWarmUp = [];
2148  // Get all the value keys to fetch...
2149  foreach ( $keys as $key ) {
2150  $keysWarmUp[] = self::VALUE_KEY_PREFIX . $key;
2151  }
2152  // Get all the check keys to fetch...
2153  foreach ( $checkKeys as $i => $checkKeyOrKeys ) {
2154  if ( is_int( $i ) ) {
2155  // Single check key that applies to all value keys
2156  $keysWarmUp[] = self::TIME_KEY_PREFIX . $checkKeyOrKeys;
2157  } else {
2158  // List of check keys that apply to value key $i
2159  $keysWarmUp = array_merge(
2160  $keysWarmUp,
2161  self::prefixCacheKeys( $checkKeyOrKeys, self::TIME_KEY_PREFIX )
2162  );
2163  }
2164  }
2165 
2166  $warmupCache = $this->cache->getMulti( $keysWarmUp );
2167  $warmupCache += array_fill_keys( $keysWarmUp, false );
2168 
2169  return $warmupCache;
2170  }
2171 
2176  protected function getCurrentTime() {
2177  return $this->wallClockOverride ?: microtime( true );
2178  }
2179 
2184  public function setMockTime( &$time ) {
2185  $this->wallClockOverride =& $time;
2186  $this->cache->setMockTime( $time );
2187  }
2188 }
WANObjectCache\ERR_UNREACHABLE
const ERR_UNREACHABLE
Definition: WANObjectCache.php:184
$time
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1795
WANObjectCache\getQoS
getQoS( $flag)
Definition: WANObjectCache.php:1710
WANObjectCache\relayDelete
relayDelete( $key)
Do the actual async bus delete of a key.
Definition: WANObjectCache.php:1849
WANObjectCache\getNonProcessCachedKeys
getNonProcessCachedKeys(array $keys, array $opts)
Definition: WANObjectCache.php:2122
WANObjectCache\VALUE_KEY_PREFIX
const VALUE_KEY_PREFIX
Definition: WANObjectCache.php:188
WANObjectCache\TTL_UNCACHEABLE
const TTL_UNCACHEABLE
Idiom for getWithSetCallback() callbacks to avoid calling set()
Definition: WANObjectCache.php:151
WANObjectCache\setMockTime
setMockTime(&$time)
Definition: WANObjectCache.php:2184
WANObjectCache\makePurgeValue
makePurgeValue( $timestamp, $holdoff)
Definition: WANObjectCache.php:2100
use
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Definition: APACHE-LICENSE-2.0.txt:10
WANObjectCache\$warmupCache
mixed[] $warmupCache
Temporary warm-up cache.
Definition: WANObjectCache.php:117
BagOStuff\ERR_UNREACHABLE
const ERR_UNREACHABLE
Definition: BagOStuff.php:82
HashBagOStuff
Simple store for keeping values in an associative array for the current process.
Definition: HashBagOStuff.php:31
EmptyBagOStuff
A BagOStuff object with no objects in it.
Definition: EmptyBagOStuff.php:29
WANObjectCache\$cache
BagOStuff $cache
The local datacenter cache.
Definition: WANObjectCache.php:89
array
the array() calling protocol came about after MediaWiki 1.4rc1.
WANObjectCache\determineKeyClass
determineKeyClass( $key)
Definition: WANObjectCache.php:2064
WANObjectCache\MAX_READ_LAG
const MAX_READ_LAG
Max replication+snapshot lag before applying TTL_LAGGED or disallowing set()
Definition: WANObjectCache.php:127
WANObjectCache\$purgeChannel
string $purgeChannel
Purge channel name.
Definition: WANObjectCache.php:93
WANObjectCache\FLD_VERSION
const FLD_VERSION
Definition: WANObjectCache.php:172
WANObjectCache\FLD_VALUE
const FLD_VALUE
Definition: WANObjectCache.php:173
string
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
WANObjectCache\MAX_COMMIT_DELAY
const MAX_COMMIT_DELAY
Max time expected to pass between delete() and DB commit finishing.
Definition: WANObjectCache.php:125
WANObjectCache\isValid
isValid( $value, $versioned, $asOf, $minTime)
Check whether $value is appropriately versioned and not older than $minTime (if set)
Definition: WANObjectCache.php:1979
WANObjectCache\getInterimValue
getInterimValue( $key, $versioned, $minTime, &$asOf)
Definition: WANObjectCache.php:1285
EventRelayerNull
No-op class for publishing messages into a PubSub system.
Definition: EventRelayerNull.php:24
WANObjectCache\makeMultiKeys
makeMultiKeys(array $entities, callable $keyFunc)
Definition: WANObjectCache.php:1628
NullStatsdDataFactory
Definition: NullStatsdDataFactory.php:10
WANObjectCache\getMultiWithSetCallback
getMultiWithSetCallback(ArrayIterator $keyedIds, $ttl, callable $callback, array $opts=[])
Method to fetch multiple cache keys at once with regeneration.
Definition: WANObjectCache.php:1383
$params
$params
Definition: styleTest.css.php:40
BagOStuff
interface is intended to be more or less compatible with the PHP memcached client.
Definition: BagOStuff.php:47
WANObjectCache\getMultiWithUnionSetCallback
getMultiWithUnionSetCallback(ArrayIterator $keyedIds, $ttl, callable $callback, array $opts=[])
Method to fetch/regenerate multiple cache keys at once.
Definition: WANObjectCache.php:1477
WANObjectCache\$processCaches
HashBagOStuff[] $processCaches
Map of group PHP instance caches.
Definition: WANObjectCache.php:91
$res
$res
Definition: database.txt:21
WANObjectCache\VFLD_VERSION
const VFLD_VERSION
Definition: WANObjectCache.php:196
$result
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! 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! 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:1993
cache
you have access to all of the normal MediaWiki so you can get a DB use the cache
Definition: maintenance.txt:55
WANObjectCache\TTL_LAGGED
const TTL_LAGGED
Max TTL to store keys when a data sourced is lagged.
Definition: WANObjectCache.php:155
BagOStuff\ERR_NONE
const ERR_NONE
Possible values for getLastError()
Definition: BagOStuff.php:80
WANObjectCache\reapCheckKey
reapCheckKey( $key, $purgeTimestamp, &$isStale=false)
Set a "check" key to soon expire in the local cluster if it pre-dates $purgeTimestamp.
Definition: WANObjectCache.php:1582
php
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
WANObjectCache\getCheckKeyTime
getCheckKeyTime( $key)
Fetch the value of a timestamp "check" key.
Definition: WANObjectCache.php:646
WANObjectCache\$logger
LoggerInterface $logger
Definition: WANObjectCache.php:103
WANObjectCache\VFLD_DATA
const VFLD_DATA
Definition: WANObjectCache.php:195
WANObjectCache\$purgeRelayer
EventRelayer $purgeRelayer
Bus that handles purge broadcasts.
Definition: WANObjectCache.php:95
WANObjectCache\$warmupKeyMisses
int $warmupKeyMisses
Key fetched.
Definition: WANObjectCache.php:119
WANObjectCache\newEmpty
static newEmpty()
Get an instance that wraps EmptyBagOStuff.
Definition: WANObjectCache.php:256
WANObjectCache\ERR_RELAY
const ERR_RELAY
Definition: WANObjectCache.php:186
WANObjectCache\TIME_KEY_PREFIX
const TIME_KEY_PREFIX
Definition: WANObjectCache.php:190
WANObjectCache\LOW_TTL
const LOW_TTL
Default remaining TTL at which to consider pre-emptive regeneration.
Definition: WANObjectCache.php:139
WANObjectCache\FLD_TIME
const FLD_TIME
Definition: WANObjectCache.php:175
IExpiringStore
Generic base class for storage interfaces.
Definition: IExpiringStore.php:31
WANObjectCache\clearProcessCache
clearProcessCache()
Clear the in-process caches; useful for testing.
Definition: WANObjectCache.php:1677
WANObjectCache\clearLastError
clearLastError()
Clear the "last error" registry.
Definition: WANObjectCache.php:1667
WANObjectCache\getCurrentTime
getCurrentTime()
Definition: WANObjectCache.php:2176
WANObjectCache\getWithSetCallback
getWithSetCallback( $key, $ttl, $callback, array $opts=[])
Method to fetch/regenerate cache keys.
Definition: WANObjectCache.php:1057
WANObjectCache\INTERIM_KEY_TTL
const INTERIM_KEY_TTL
Seconds to keep interim value keys for tombstoned keys around.
Definition: WANObjectCache.php:134
WANObjectCache\prefixCacheKeys
static prefixCacheKeys(array $keys, $prefix)
Definition: WANObjectCache.php:2051
WANObjectCache\unwrap
unwrap( $wrapped, $now)
Do not use this method outside WANObjectCache.
Definition: WANObjectCache.php:2013
WANObjectCache\MIN_TIMESTAMP_NONE
const MIN_TIMESTAMP_NONE
Idiom for getWithSetCallback() for "no minimum required as-of timestamp".
Definition: WANObjectCache.php:164
WANObjectCache\getLastError
getLastError()
Get the "last error" registered; clearLastError() should be called manually.
Definition: WANObjectCache.php:1641
WANObjectCache\INTERIM_KEY_PREFIX
const INTERIM_KEY_PREFIX
Definition: WANObjectCache.php:189
WANObjectCache\__construct
__construct(array $params)
Definition: WANObjectCache.php:227
WANObjectCache\useInterimHoldOffCaching
useInterimHoldOffCaching( $enabled)
Enable or disable the use of brief caching for tombstoned keys.
Definition: WANObjectCache.php:1701
WANObjectCache\reap
reap( $key, $purgeTimestamp, &$isStale=false)
Set a key to soon expire in the local cluster if it pre-dates $purgeTimestamp.
Definition: WANObjectCache.php:1553
WANObjectCache\touchCheckKey
touchCheckKey( $key, $holdoff=self::HOLDOFF_TTL)
Purge a "check" key from all datacenters, invalidating keys that use it.
Definition: WANObjectCache.php:776
WANObjectCache\DEFAULT_PURGE_CHANNEL
const DEFAULT_PURGE_CHANNEL
Definition: WANObjectCache.php:200
WANObjectCache\ERR_NONE
const ERR_NONE
Definition: WANObjectCache.php:182
WANObjectCache\adaptiveTTL
adaptiveTTL( $mtime, $maxTTL, $minTTL=30, $factor=0.2)
Get a TTL that is higher for objects that have not changed recently.
Definition: WANObjectCache.php:1777
WANObjectCache\relayPurge
relayPurge( $key, $ttl, $holdoff)
Do the actual async bus purge of a key.
Definition: WANObjectCache.php:1809
WANObjectCache\$wallClockOverride
float null $wallClockOverride
Definition: WANObjectCache.php:122
WANObjectCache\HOLDOFF_TTL
const HOLDOFF_TTL
Seconds to tombstone keys on delete()
Definition: WANObjectCache.php:129
list
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
WANObjectCache\getMultiCheckKeyTime
getMultiCheckKeyTime(array $keys)
Fetch the values of each timestamp "check" key.
Definition: WANObjectCache.php:711
WANObjectCache\worthRefreshPopular
worthRefreshPopular( $asOf, $ageNew, $timeTillRefresh, $now)
Check if a key is due for randomized regeneration due to its popularity.
Definition: WANObjectCache.php:1944
WANObjectCache\isAliveOrInGracePeriod
isAliveOrInGracePeriod( $curTTL, $graceTTL)
Check if a key is fresh or in the grace window and thus due for randomized reuse.
Definition: WANObjectCache.php:1885
WANObjectCache\ERR_NO_RESPONSE
const ERR_NO_RESPONSE
Definition: WANObjectCache.php:183
WANObjectCache\RAMPUP_TTL
const RAMPUP_TTL
Seconds to ramp up to the "popularity" refresh chance after a key is no longer new.
Definition: WANObjectCache.php:148
WANObjectCache\wrap
wrap( $value, $ttl, $now)
Do not use this method outside WANObjectCache.
Definition: WANObjectCache.php:1997
WANObjectCache\$useInterimHoldOffCaching
bool $useInterimHoldOffCaching
Whether to use "interim" caching while keys are tombstoned.
Definition: WANObjectCache.php:107
$value
$value
Definition: styleTest.css.php:45
WANObjectCache\MUTEX_KEY_PREFIX
const MUTEX_KEY_PREFIX
Definition: WANObjectCache.php:191
WANObjectCache\PC_PRIMARY
const PC_PRIMARY
Definition: WANObjectCache.php:198
WANObjectCache\makeGlobalKey
makeGlobalKey( $class, $component=null)
Definition: WANObjectCache.php:1618
WANObjectCache\$cluster
string $cluster
Cache cluster name for mcrouter use.
Definition: WANObjectCache.php:101
WANObjectCache\worthRefreshExpiring
worthRefreshExpiring( $curTTL, $lowTTL)
Check if a key is nearing expiration and thus due for randomized regeneration.
Definition: WANObjectCache.php:1915
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:87
WANObjectCache\VERSION
const VERSION
Cache format version number.
Definition: WANObjectCache.php:170
WANObjectCache\getProcessCache
getProcessCache( $group)
Definition: WANObjectCache.php:2108
WANObjectCache\makeKey
makeKey( $class, $component=null)
Definition: WANObjectCache.php:1607
WANObjectCache\getRawKeysForWarmup
getRawKeysForWarmup(array $keys, array $checkKeys)
Definition: WANObjectCache.php:2142
WANObjectCache\getMulti
getMulti(array $keys, &$curTTLs=[], array $checkKeys=[], array &$asOfs=[])
Fetch the value of several keys from cache.
Definition: WANObjectCache.php:325
WANObjectCache\doGetWithSetCallback
doGetWithSetCallback( $key, $ttl, $callback, array $opts, &$asOf=null)
Do the actual I/O for getWithSetCallback() when needed.
Definition: WANObjectCache.php:1142
WANObjectCache\resetCheckKey
resetCheckKey( $key)
Delete a "check" key from all datacenters, invalidating keys that use it.
Definition: WANObjectCache.php:808
WANObjectCache\TSE_NONE
const TSE_NONE
Idiom for getWithSetCallback() callbacks to 'lockTSE' logic.
Definition: WANObjectCache.php:153
WANObjectCache\parsePurgeValue
parsePurgeValue( $value)
Definition: WANObjectCache.php:2075
WANObjectCache\HOT_TTR
const HOT_TTR
The time length of the "popularity" refresh window for hot keys.
Definition: WANObjectCache.php:144
WANObjectCache\LOCK_TTL
const LOCK_TTL
Seconds to keep lock keys around.
Definition: WANObjectCache.php:137
$code
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:865
WANObjectCache\PURGE_VAL_PREFIX
const PURGE_VAL_PREFIX
Definition: WANObjectCache.php:193
WANObjectCache\$asyncHandler
callable null $asyncHandler
Function that takes a WAN cache callback and runs it later.
Definition: WANObjectCache.php:109
WANObjectCache\getWarmupKeyMisses
getWarmupKeyMisses()
Definition: WANObjectCache.php:1795
as
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
Definition: distributors.txt:22
WANObjectCache\$mcrouterAware
$mcrouterAware
@bar bool Whether to use mcrouter key prefixing for routing
Definition: WANObjectCache.php:97
IExpiringStore\TTL_YEAR
const TTL_YEAR
Definition: IExpiringStore.php:39
$keys
$keys
Definition: testCompression.php:67
WANObjectCache\$lastRelayError
int $lastRelayError
ERR_* constant for the "last error" registry.
Definition: WANObjectCache.php:112
WANObjectCache\HOLDOFF_NONE
const HOLDOFF_NONE
Idiom for delete() for "no hold-off".
Definition: WANObjectCache.php:157
WANObjectCache\$callbackDepth
int $callbackDepth
Callback stack depth for getWithSetCallback()
Definition: WANObjectCache.php:115
WANObjectCache\FLD_FLAGS
const FLD_FLAGS
Definition: WANObjectCache.php:176
WANObjectCache\AGE_NEW
const AGE_NEW
Never consider performing "popularity" refreshes until a key reaches this age.
Definition: WANObjectCache.php:142
WANObjectCache\setLogger
setLogger(LoggerInterface $logger)
Definition: WANObjectCache.php:247
WANObjectCache\setInterimValue
setInterimValue( $key, $wrapped, $tempTTL)
Definition: WANObjectCache.php:1306
WANObjectCache\TINY_NEGATIVE
const TINY_NEGATIVE
Tiny negative float to use when CTL comes up >= 0 due to clock skew.
Definition: WANObjectCache.php:167
WANObjectCache\CHECK_KEY_TTL
const CHECK_KEY_TTL
Seconds to keep dependency purge keys around.
Definition: WANObjectCache.php:132
WANObjectCache\GRACE_TTL_NONE
const GRACE_TTL_NONE
Idiom for set()/getWithSetCallback() for "no post-expired grace period".
Definition: WANObjectCache.php:161
EventRelayer
Base class for reliable event relays.
Definition: EventRelayer.php:27
WANObjectCache\FLD_HOLDOFF
const FLD_HOLDOFF
Definition: WANObjectCache.php:177
BagOStuff\ERR_NO_RESPONSE
const ERR_NO_RESPONSE
Definition: BagOStuff.php:81
WANObjectCache\ERR_UNEXPECTED
const ERR_UNEXPECTED
Definition: WANObjectCache.php:185
WANObjectCache\FLD_TTL
const FLD_TTL
Definition: WANObjectCache.php:174
WANObjectCache\HIT_RATE_HIGH
const HIT_RATE_HIGH
Hits/second for a refresh to be expected within the "popularity" window.
Definition: WANObjectCache.php:146
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
WANObjectCache\STALE_TTL_NONE
const STALE_TTL_NONE
Idiom for set()/getWithSetCallback() for "do not augment the storage medium TTL".
Definition: WANObjectCache.php:159
WANObjectCache\$stats
StatsdDataFactoryInterface $stats
Definition: WANObjectCache.php:105
WANObjectCache\processCheckKeys
processCheckKeys(array $timeKeys, array $wrappedValues, $now)
Definition: WANObjectCache.php:416
WANObjectCache\$region
string $region
Physical region for mcrouter use.
Definition: WANObjectCache.php:99