MediaWiki master
WANObjectCache.php
Go to the documentation of this file.
1<?php
8
9use ArrayIterator;
10use Closure;
11use Exception;
12use Psr\Log\LoggerAwareInterface;
13use Psr\Log\LoggerInterface;
14use Psr\Log\NullLogger;
15use RuntimeException;
16use UnexpectedValueException;
22
148class WANObjectCache implements
151 LoggerAwareInterface
152{
154 protected $cache;
156 protected $processCaches = [];
158 protected $logger;
160 protected $stats;
162 protected $asyncHandler;
163
175 protected $epoch;
178
180 private $tracer;
181
183 private $missLog;
184
186 private $callbackDepth = 0;
188 private $warmupCache = [];
190 private $warmupKeyMisses = 0;
191
193 private $wallClockOverride;
194
196 private const MAX_COMMIT_DELAY = 3;
198 private const MAX_READ_LAG = 7;
200 public const HOLDOFF_TTL = self::MAX_COMMIT_DELAY + self::MAX_READ_LAG + 1;
201
203 private const LOW_TTL = 60;
205 public const TTL_LAGGED = 30;
206
208 private const HOT_TTR = 900;
210 private const AGE_NEW = 60;
211
213 private const TSE_NONE = -1;
214
216 public const STALE_TTL_NONE = 0;
218 public const GRACE_TTL_NONE = 0;
220 public const HOLDOFF_TTL_NONE = 0;
221
223 public const MIN_TIMESTAMP_NONE = 0.0;
224
226 private const PC_PRIMARY = 'primary:1000';
227
229 public const PASS_BY_REF = [];
230
232 private const SCHEME_HASH_TAG = 1;
234 private const SCHEME_HASH_STOP = 2;
235
237 private const CHECK_KEY_TTL = self::TTL_YEAR;
239 private const INTERIM_KEY_TTL = 2;
240
242 private const LOCK_TTL = 10;
244 private const RAMPUP_TTL = 30;
245
247 private const TINY_NEGATIVE = -0.000001;
249 private const TINY_POSITIVE = 0.000001;
250
252 private const RECENT_SET_LOW_MS = 50;
254 private const RECENT_SET_HIGH_MS = 100;
255
257 private const GENERATION_HIGH_SEC = 0.2;
258
260 private const PURGE_TIME = 0;
262 private const PURGE_HOLDOFF = 1;
263
265 private const VERSION = 1;
266
268 public const KEY_VERSION = 'version';
270 public const KEY_AS_OF = 'asOf';
272 public const KEY_TTL = 'ttl';
274 public const KEY_CUR_TTL = 'curTTL';
276 public const KEY_TOMB_AS_OF = 'tombAsOf';
278 public const KEY_CHECK_AS_OF = 'lastCKPurge';
279
281 private const RES_VALUE = 0;
283 private const RES_VERSION = 1;
285 private const RES_AS_OF = 2;
287 private const RES_TTL = 3;
289 private const RES_TOMB_AS_OF = 4;
291 private const RES_CHECK_AS_OF = 5;
293 private const RES_TOUCH_AS_OF = 6;
295 private const RES_CUR_TTL = 7;
296
298 private const FLD_FORMAT_VERSION = 0;
300 private const FLD_VALUE = 1;
302 private const FLD_TTL = 2;
304 private const FLD_TIME = 3;
306 private const FLD_FLAGS = 4;
308 private const FLD_VALUE_VERSION = 5;
309 private const FLD_GENERATION_TIME = 6;
310
312 private const TYPE_VALUE = 'v';
314 private const TYPE_TIMESTAMP = 't';
316 private const TYPE_MUTEX = 'm';
318 private const TYPE_INTERIM = 'i';
319
321 private const PURGE_VAL_PREFIX = 'PURGED';
322
350 public function __construct( array $params ) {
351 $this->cache = $params['cache'];
352 $this->broadcastRoute = $params['broadcastRoutingPrefix'] ?? null;
353 $this->epoch = $params['epoch'] ?? 0;
354 if ( ( $params['coalesceScheme'] ?? '' ) === 'hash_tag' ) {
355 // https://redis.io/topics/cluster-spec
356 // https://github.com/twitter/twemproxy/blob/v0.4.1/notes/recommendation.md#hash-tags
357 // https://github.com/Netflix/dynomite/blob/v0.7.0/notes/recommendation.md#hash-tags
358 $this->coalesceScheme = self::SCHEME_HASH_TAG;
359 } else {
360 // https://github.com/facebook/mcrouter/wiki/Key-syntax
361 $this->coalesceScheme = self::SCHEME_HASH_STOP;
362 }
363
364 $this->setLogger( $params['logger'] ?? new NullLogger() );
365 $this->tracer = $params['tracer'] ?? new NoopTracer();
366 $this->stats = $params['stats'] ?? StatsFactory::newNull();
367
368 $this->asyncHandler = $params['asyncHandler'] ?? null;
369 $this->missLog = array_fill( 0, 10, [ '', 0.0 ] );
370 }
371
372 public function setLogger( LoggerInterface $logger ): void {
373 $this->logger = $logger;
374 }
375
379 public static function newEmpty(): static {
380 return new static( [ 'cache' => new EmptyBagOStuff() ] );
381 }
382
438 final public function get( $key, &$curTTL = null, array $checkKeys = [], &$info = [] ) {
439 // Note that an undeclared variable passed as $info starts as null (not the default).
440 // Also, if no $info parameter is provided, then it doesn't matter how it changes here.
441 $legacyInfo = ( $info !== self::PASS_BY_REF );
442
444 $span = $this->startOperationSpan( __FUNCTION__, $key, $checkKeys );
445
446 $now = $this->getCurrentTime();
447 $res = $this->fetchKeys( [ $key ], $checkKeys, $now )[$key];
448
449 $curTTL = $res[self::RES_CUR_TTL];
450 $info = $legacyInfo
451 ? $res[self::RES_AS_OF]
452 : [
453 self::KEY_VERSION => $res[self::RES_VERSION],
454 self::KEY_AS_OF => $res[self::RES_AS_OF],
455 self::KEY_TTL => $res[self::RES_TTL],
456 self::KEY_CUR_TTL => $res[self::RES_CUR_TTL],
457 self::KEY_TOMB_AS_OF => $res[self::RES_TOMB_AS_OF],
458 self::KEY_CHECK_AS_OF => $res[self::RES_CHECK_AS_OF]
459 ];
460
461 if ( $curTTL === null || $curTTL <= 0 ) {
462 // Log the timestamp in case a corresponding set() call does not provide "walltime"
463 unset( $this->missLog[array_key_first( $this->missLog )] );
464 $this->missLog[] = [ $key, $this->getCurrentTime() ];
465 }
466
467 return $res[self::RES_VALUE];
468 }
469
494 final public function getMulti(
495 array $keys,
496 &$curTTLs = [],
497 array $checkKeys = [],
498 &$info = []
499 ) {
500 // Note that an undeclared variable passed as $info starts as null (not the default).
501 // Also, if no $info parameter is provided, then it doesn't matter how it changes here.
502 $legacyInfo = ( $info !== self::PASS_BY_REF );
503
505 $span = $this->startOperationSpan( __FUNCTION__, $keys, $checkKeys );
506
507 $curTTLs = [];
508 $info = [];
509 $valuesByKey = [];
510
511 $now = $this->getCurrentTime();
512 $resByKey = $this->fetchKeys( $keys, $checkKeys, $now );
513 foreach ( $resByKey as $key => $res ) {
514 if ( $res[self::RES_VALUE] !== false ) {
515 $valuesByKey[$key] = $res[self::RES_VALUE];
516 }
517
518 if ( $res[self::RES_CUR_TTL] !== null ) {
519 $curTTLs[$key] = $res[self::RES_CUR_TTL];
520 }
521 $info[$key] = $legacyInfo
522 ? $res[self::RES_AS_OF]
523 : [
524 self::KEY_VERSION => $res[self::RES_VERSION],
525 self::KEY_AS_OF => $res[self::RES_AS_OF],
526 self::KEY_TTL => $res[self::RES_TTL],
527 self::KEY_CUR_TTL => $res[self::RES_CUR_TTL],
528 self::KEY_TOMB_AS_OF => $res[self::RES_TOMB_AS_OF],
529 self::KEY_CHECK_AS_OF => $res[self::RES_CHECK_AS_OF]
530 ];
531 }
532
533 return $valuesByKey;
534 }
535
551 protected function fetchKeys( array $keys, array $checkKeys, float $now, $touchedCb = null ) {
552 $resByKey = [];
553
554 // List of all sister keys that need to be fetched from cache
555 $allSisterKeys = [];
556 // Order-corresponding value sister key list for the base key list ($keys)
557 $valueSisterKeys = [];
558 // List of "check" sister keys to compare all value sister keys against
559 $checkSisterKeysForAll = [];
560 // Map of (base key => additional "check" sister key(s) to compare against)
561 $checkSisterKeysByKey = [];
562
563 foreach ( $keys as $key ) {
564 $sisterKey = $this->makeSisterKey( $key, self::TYPE_VALUE );
565 $allSisterKeys[] = $sisterKey;
566 $valueSisterKeys[] = $sisterKey;
567 }
568
569 foreach ( $checkKeys as $i => $checkKeyOrKeyGroup ) {
570 // Note: avoid array_merge() inside loop in case there are many keys
571 if ( is_int( $i ) ) {
572 // Single "check" key that applies to all base keys
573 $sisterKey = $this->makeSisterKey( $checkKeyOrKeyGroup, self::TYPE_TIMESTAMP );
574 $allSisterKeys[] = $sisterKey;
575 $checkSisterKeysForAll[] = $sisterKey;
576 } else {
577 // List of "check" keys that apply to a specific base key
578 foreach ( (array)$checkKeyOrKeyGroup as $checkKey ) {
579 $sisterKey = $this->makeSisterKey( $checkKey, self::TYPE_TIMESTAMP );
580 $allSisterKeys[] = $sisterKey;
581 $checkSisterKeysByKey[$i][] = $sisterKey;
582 }
583 }
584 }
585
586 if ( $this->warmupCache ) {
587 // Get the wrapped values of the sister keys from the warmup cache
588 $wrappedBySisterKey = $this->warmupCache;
589 $sisterKeysMissing = array_diff( $allSisterKeys, array_keys( $wrappedBySisterKey ) );
590 if ( $sisterKeysMissing ) {
591 $this->warmupKeyMisses += count( $sisterKeysMissing );
592 $wrappedBySisterKey += $this->cache->getMulti( $sisterKeysMissing );
593 }
594 } else {
595 // Fetch the wrapped values of the sister keys from the backend
596 $wrappedBySisterKey = $this->cache->getMulti( $allSisterKeys );
597 }
598
599 // List of "check" sister key purge timestamps to compare all value sister keys against
600 $ckPurgesForAll = $this->processCheckKeys(
601 $checkSisterKeysForAll,
602 $wrappedBySisterKey,
603 $now
604 );
605 // Map of (base key => extra "check" sister key purge timestamp(s) to compare against)
606 $ckPurgesByKey = [];
607 foreach ( $checkSisterKeysByKey as $keyWithCheckKeys => $checkKeysForKey ) {
608 $ckPurgesByKey[$keyWithCheckKeys] = $this->processCheckKeys(
609 $checkKeysForKey,
610 $wrappedBySisterKey,
611 $now
612 );
613 }
614
615 // Unwrap and validate any value found for each base key (under the value sister key)
616 foreach (
617 array_map( null, $valueSisterKeys, $keys )
618 as [ $valueSisterKey, $key ]
619 ) {
620 if ( array_key_exists( $valueSisterKey, $wrappedBySisterKey ) ) {
621 // Key exists as either a live value or tombstone value
622 $wrapped = $wrappedBySisterKey[$valueSisterKey];
623 } else {
624 // Key does not exist
625 $wrapped = false;
626 }
627
628 $res = $this->unwrap( $wrapped, $now );
629 $value = $res[self::RES_VALUE];
630
631 foreach ( array_merge( $ckPurgesForAll, $ckPurgesByKey[$key] ?? [] ) as $ckPurge ) {
632 $res[self::RES_CHECK_AS_OF] = max(
633 $ckPurge[self::PURGE_TIME],
634 $res[self::RES_CHECK_AS_OF]
635 );
636 // Timestamp marking the end of the hold-off period for this purge
637 $holdoffDeadline = $ckPurge[self::PURGE_TIME] + $ckPurge[self::PURGE_HOLDOFF];
638 // Check if the value was generated during the hold-off period
639 if ( $value !== false && $holdoffDeadline >= $res[self::RES_AS_OF] ) {
640 // How long ago this value was purged by *this* "check" key
641 $ago = min( $ckPurge[self::PURGE_TIME] - $now, self::TINY_NEGATIVE );
642 // How long ago this value was purged by *any* known "check" key
643 $res[self::RES_CUR_TTL] = min( $res[self::RES_CUR_TTL], $ago );
644 }
645 }
646
647 if ( $touchedCb !== null && $value !== false ) {
648 $touched = $touchedCb( $value );
649 if ( $touched !== null && $touched >= $res[self::RES_AS_OF] ) {
650 $res[self::RES_CUR_TTL] = min(
651 $res[self::RES_CUR_TTL],
652 $res[self::RES_AS_OF] - $touched,
653 self::TINY_NEGATIVE
654 );
655 }
656 } else {
657 $touched = null;
658 }
659
660 $res[self::RES_TOUCH_AS_OF] = max( $res[self::RES_TOUCH_AS_OF], $touched );
661
662 $resByKey[$key] = $res;
663 }
664
665 return $resByKey;
666 }
667
674 private function processCheckKeys(
675 array $checkSisterKeys,
676 array $wrappedBySisterKey,
677 float $now
678 ) {
679 $purges = [];
680
681 foreach ( $checkSisterKeys as $timeKey ) {
682 $purge = isset( $wrappedBySisterKey[$timeKey] )
683 ? $this->parsePurgeValue( $wrappedBySisterKey[$timeKey] )
684 : null;
685
686 if ( $purge === null ) {
687 // No holdoff when lazy creating a check key, use cache right away (T344191)
688 $wrapped = $this->makeCheckPurgeValue( $now, self::HOLDOFF_TTL_NONE, $purge );
689 $this->cache->add(
690 $timeKey,
691 $wrapped,
692 self::CHECK_KEY_TTL,
693 $this->cache::WRITE_BACKGROUND
694 );
695 }
696
697 $purges[] = $purge;
698 }
699
700 return $purges;
701 }
702
786 final public function set( $key, $value, $ttl = self::TTL_INDEFINITE, array $opts = [] ) {
788 $span = $this->startOperationSpan( __FUNCTION__, $key );
789
790 $keygroup = $this->determineKeyGroupForStats( $key );
791
792 $ok = $this->setMainValue(
793 $key,
794 $value,
795 $ttl,
796 $opts['version'] ?? null,
797 $opts['walltime'] ?? null,
798 $opts['lag'] ?? 0,
799 $opts['since'] ?? null,
800 $opts['pending'] ?? false,
801 $opts['lockTSE'] ?? self::TSE_NONE,
802 $opts['staleTTL'] ?? self::STALE_TTL_NONE,
803 $opts['segmentable'] ?? false,
804 $opts['creating'] ?? false
805 );
806
807 $this->stats->getCounter( 'wanobjectcache_set_total' )
808 ->setLabel( 'keygroup', $keygroup )
809 ->setLabel( 'result', ( $ok ? 'ok' : 'error' ) )
810 ->increment();
811
812 return $ok;
813 }
814
830 private function setMainValue(
831 $key,
832 $value,
833 $ttl,
834 ?int $version,
835 ?float $walltime,
836 $dataReplicaLag,
837 $dataReadSince,
838 bool $dataPendingCommit,
839 int $lockTSE,
840 int $staleTTL,
841 bool $segmentable,
842 bool $creating
843 ) {
844 if ( $ttl < 0 ) {
845 // not cacheable
846 return true;
847 }
848
849 $now = $this->getCurrentTime();
850
851 // T413673: Handle PHP8.5 case where TTL is infinite.
852 if ( is_finite( $ttl ) ) {
853 $ttl = (int)$ttl;
854 } else {
855 $ttl = self::TTL_INDEFINITE;
856 }
857 $walltime ??= $this->timeSinceLoggedMiss( $key, $now );
858 $dataSnapshotLag = ( $dataReadSince !== null ) ? max( 0, $now - $dataReadSince ) : 0;
859 $dataCombinedLag = $dataReplicaLag + $dataSnapshotLag;
860
861 // Forbid caching data that only exists within an uncommitted transaction. Also, lower
862 // the TTL when the data has a "since" time so far in the past that a delete() tombstone,
863 // made after that time, could have already expired (the key is no longer write-holed).
864 // The mitigation TTL depends on whether this data lag is assumed to systemically effect
865 // regeneration attempts in the near future. The TTL also reflects regeneration wall time.
866 if ( $dataPendingCommit ) {
867 // Case A: data comes from an uncommitted write transaction
868 $mitigated = 'pending writes';
869 // Data might never be committed; rely on a less problematic regeneration attempt
870 $mitigationTTL = self::TTL_UNCACHEABLE;
871 } elseif ( $dataSnapshotLag > self::MAX_READ_LAG ) {
872 // Case B: high snapshot lag
873 $pregenSnapshotLag = ( $walltime !== null ) ? ( $dataSnapshotLag - $walltime ) : 0;
874 if ( ( $pregenSnapshotLag + self::GENERATION_HIGH_SEC ) > self::MAX_READ_LAG ) {
875 // Case B1: generation started when transaction duration was already long
876 $mitigated = 'snapshot lag (late generation)';
877 // Probably non-systemic; rely on a less problematic regeneration attempt
878 $mitigationTTL = self::TTL_UNCACHEABLE;
879 } else {
880 // Case B2: slow generation made transaction duration long
881 $mitigated = 'snapshot lag (high generation time)';
882 // Probably systemic; use a low TTL to avoid stampedes/uncacheability
883 $mitigationTTL = self::TTL_LAGGED;
884 }
885 } elseif ( $dataReplicaLag === false || $dataReplicaLag > self::MAX_READ_LAG ) {
886 // Case C: low/medium snapshot lag with high replication lag
887 $mitigated = 'replication lag';
888 // Probably systemic; use a low TTL to avoid stampedes/uncacheability
889 $mitigationTTL = self::TTL_LAGGED;
890 } elseif ( $dataCombinedLag > self::MAX_READ_LAG ) {
891 $pregenCombinedLag = ( $walltime !== null ) ? ( $dataCombinedLag - $walltime ) : 0;
892 // Case D: medium snapshot lag with medium replication lag
893 if ( ( $pregenCombinedLag + self::GENERATION_HIGH_SEC ) > self::MAX_READ_LAG ) {
894 // Case D1: generation started when read lag was too high
895 $mitigated = 'read lag (late generation)';
896 // Probably non-systemic; rely on a less problematic regeneration attempt
897 $mitigationTTL = self::TTL_UNCACHEABLE;
898 } else {
899 // Case D2: slow generation made read lag too high
900 $mitigated = 'read lag (high generation time)';
901 // Probably systemic; use a low TTL to avoid stampedes/uncacheability
902 $mitigationTTL = self::TTL_LAGGED;
903 }
904 } else {
905 // Case E: new value generated with recent data
906 $mitigated = null;
907 // Nothing to mitigate
908 $mitigationTTL = null;
909 }
910
911 if ( $mitigationTTL === self::TTL_UNCACHEABLE ) {
912 $this->logger->warning(
913 "Rejected set() for {cachekey} due to $mitigated.",
914 [
915 'cachekey' => $key,
916 'lag' => $dataReplicaLag,
917 'age' => $dataSnapshotLag,
918 'walltime' => $walltime
919 ]
920 );
921
922 // no-op the write for being unsafe
923 return true;
924 }
925
926 // TTL to use in staleness checks (does not effect persistence layer TTL)
927 $logicalTTL = null;
928
929 if ( $mitigationTTL !== null ) {
930 // New value was generated from data that is old enough to be risky
931 if ( $lockTSE >= 0 ) {
932 // Persist the value as long as normal, but make it count as stale sooner
933 $logicalTTL = min( $ttl ?: INF, $mitigationTTL );
934 } else {
935 // Persist the value for a shorter duration
936 $ttl = min( $ttl ?: INF, $mitigationTTL );
937 }
938
939 $this->logger->warning(
940 "Lowered set() TTL for {cachekey} due to $mitigated.",
941 [
942 'cachekey' => $key,
943 'lag' => $dataReplicaLag,
944 'age' => $dataSnapshotLag,
945 'walltime' => $walltime
946 ]
947 );
948 }
949
950 // Wrap that value with time/TTL/version metadata
951 $wrapped = $this->wrap( $value, $logicalTTL ?: $ttl, $version, $now );
952 $storeTTL = $ttl + $staleTTL;
953
954 $flags = $this->cache::WRITE_BACKGROUND;
955 if ( $segmentable ) {
956 $flags |= $this->cache::WRITE_ALLOW_SEGMENTS;
957 }
958
959 if ( $creating ) {
960 $ok = $this->cache->add(
961 $this->makeSisterKey( $key, self::TYPE_VALUE ),
962 $wrapped,
963 $storeTTL,
964 $flags
965 );
966 } else {
967 $ok = $this->cache->merge(
968 $this->makeSisterKey( $key, self::TYPE_VALUE ),
969 static function ( $cache, $key, $cWrapped ) use ( $wrapped ) {
970 // A string value means that it is a tombstone; do nothing in that case
971 return ( is_string( $cWrapped ) ) ? false : $wrapped;
972 },
973 $storeTTL,
974 $this->cache::MAX_CONFLICTS_ONE,
975 $flags
976 );
977 }
978
979 return $ok;
980 }
981
1044 final public function delete( $key, $ttl = self::HOLDOFF_TTL ) {
1046 $span = $this->startOperationSpan( __FUNCTION__, $key );
1047
1048 // Purge values must be stored under the value key so that WANObjectCache::set()
1049 // can atomically merge values without accidentally undoing a recent purge and thus
1050 // violating the holdoff TTL restriction.
1051 $valueSisterKey = $this->makeSisterKey( $key, self::TYPE_VALUE );
1052
1053 if ( $ttl <= 0 ) {
1054 // A client or cache cleanup script is requesting a cache purge, so there is no
1055 // volatility period due to replica DB lag. Any recent change to an entity cached
1056 // in this key should have triggered an appropriate purge event.
1057 $ok = $this->cache->delete( $this->getRouteKey( $valueSisterKey ), $this->cache::WRITE_BACKGROUND );
1058 } else {
1059 // A cacheable entity recently changed, so there might be a volatility period due
1060 // to replica DB lag. Clients usually expect their actions to be reflected in any
1061 // of their subsequent web request. This is attainable if (a) purge relay lag is
1062 // lower than the time it takes for subsequent request by the client to arrive,
1063 // and, (b) DB replica queries have "read-your-writes" consistency due to DB lag
1064 // mitigation systems.
1065 $now = $this->getCurrentTime();
1066 // Set the key to the purge value in all datacenters
1067 $purge = self::PURGE_VAL_PREFIX . ':' . (int)$now;
1068 $ok = $this->cache->set(
1069 $this->getRouteKey( $valueSisterKey ),
1070 $purge,
1071 $ttl,
1072 $this->cache::WRITE_BACKGROUND
1073 );
1074 }
1075
1076 $keygroup = $this->determineKeyGroupForStats( $key );
1077
1078 $this->stats->getCounter( 'wanobjectcache_delete_total' )
1079 ->setLabel( 'keygroup', $keygroup )
1080 ->setLabel( 'result', ( $ok ? 'ok' : 'error' ) )
1081 ->increment();
1082
1083 return $ok;
1084 }
1085
1105 final public function getCheckKeyTime( $key ) {
1107 $span = $this->startOperationSpan( __FUNCTION__, $key );
1108
1109 return $this->getMultiCheckKeyTime( [ $key ] )[$key];
1110 }
1111
1173 final public function getMultiCheckKeyTime( array $keys ) {
1175 $span = $this->startOperationSpan( __FUNCTION__, $keys );
1176
1177 $checkSisterKeysByKey = [];
1178 foreach ( $keys as $key ) {
1179 $checkSisterKeysByKey[$key] = $this->makeSisterKey( $key, self::TYPE_TIMESTAMP );
1180 }
1181
1182 $wrappedBySisterKey = $this->cache->getMulti( $checkSisterKeysByKey );
1183 $wrappedBySisterKey += array_fill_keys( $checkSisterKeysByKey, false );
1184
1185 $now = $this->getCurrentTime();
1186 $times = [];
1187 foreach ( $checkSisterKeysByKey as $key => $checkSisterKey ) {
1188 $purge = $this->parsePurgeValue( $wrappedBySisterKey[$checkSisterKey] );
1189 if ( $purge === null ) {
1190 $wrapped = $this->makeCheckPurgeValue( $now, self::HOLDOFF_TTL_NONE, $purge );
1191 $this->cache->add(
1192 $checkSisterKey,
1193 $wrapped,
1194 self::CHECK_KEY_TTL,
1195 $this->cache::WRITE_BACKGROUND
1196 );
1197 }
1198
1199 $times[$key] = $purge[self::PURGE_TIME];
1200 }
1201
1202 return $times;
1203 }
1204
1238 public function touchCheckKey( $key, $holdoff = self::HOLDOFF_TTL ) {
1240 $span = $this->startOperationSpan( __FUNCTION__, $key );
1241
1242 $checkSisterKey = $this->makeSisterKey( $key, self::TYPE_TIMESTAMP );
1243
1244 $now = $this->getCurrentTime();
1245 $purge = $this->makeCheckPurgeValue( $now, $holdoff );
1246 $ok = $this->cache->set(
1247 $this->getRouteKey( $checkSisterKey ),
1248 $purge,
1249 self::CHECK_KEY_TTL,
1250 $this->cache::WRITE_BACKGROUND
1251 );
1252
1253 $keygroup = $this->determineKeyGroupForStats( $key );
1254
1255 $this->stats->getCounter( 'wanobjectcache_check_total' )
1256 ->setLabel( 'keygroup', $keygroup )
1257 ->setLabel( 'result', ( $ok ? 'ok' : 'error' ) )
1258 ->increment();
1259
1260 return $ok;
1261 }
1262
1290 public function resetCheckKey( $key ) {
1292 $span = $this->startOperationSpan( __FUNCTION__, $key );
1293
1294 $checkSisterKey = $this->makeSisterKey( $key, self::TYPE_TIMESTAMP );
1295 $ok = $this->cache->delete( $this->getRouteKey( $checkSisterKey ), $this->cache::WRITE_BACKGROUND );
1296
1297 $keygroup = $this->determineKeyGroupForStats( $key );
1298
1299 $this->stats->getCounter( 'wanobjectcache_reset_total' )
1300 ->setLabel( 'keygroup', $keygroup )
1301 ->setLabel( 'result', ( $ok ? 'ok' : 'error' ) )
1302 ->increment();
1303
1304 return $ok;
1305 }
1306
1617 final public function getWithSetCallback(
1618 $key, $ttl, $callback, array $opts = [], array $cbParams = []
1619 ) {
1621 $span = $this->startOperationSpan( __FUNCTION__, $key );
1622
1623 $version = $opts['version'] ?? null;
1624 $pcTTL = $opts['pcTTL'] ?? self::TTL_UNCACHEABLE;
1625 $pCache = ( $pcTTL >= 0 )
1626 ? $this->getProcessCache( $opts['pcGroup'] ?? self::PC_PRIMARY )
1627 : null;
1628
1629 // Use the process cache if requested as long as no outer cache callback is running.
1630 // Nested callback process cache use is not lag-safe with regard to HOLDOFF_TTL since
1631 // process cached values are more lagged than persistent ones as they are not purged.
1632 if ( $pCache && $this->callbackDepth == 0 ) {
1633 $cached = $pCache->get( $key, $pcTTL, false );
1634 if ( $cached !== false ) {
1635 $this->logger->debug( "getWithSetCallback($key): process cache hit" );
1636 return $cached;
1637 }
1638 }
1639
1640 [ $value, $valueVersion, $curAsOf ] = $this->fetchOrRegenerate( $key, $ttl, $callback, $opts, $cbParams );
1641 if ( $valueVersion !== $version ) {
1642 // Current value has a different version; use the variant key for this version.
1643 // Regenerate the variant value if it is not newer than the main value at $key
1644 // so that purges to the main key propagate to the variant value.
1645 $this->logger->debug( "getWithSetCallback($key): using variant key" );
1646 [ $value ] = $this->fetchOrRegenerate(
1647 $this->makeGlobalKey( 'WANCache-key-variant', md5( $key ), (string)$version ),
1648 $ttl,
1649 $callback,
1650 [ 'version' => null, 'minAsOf' => $curAsOf ] + $opts,
1651 $cbParams
1652 );
1653 }
1654
1655 // Update the process cache if enabled
1656 if ( $pCache && $value !== false ) {
1657 $pCache->set( $key, $value );
1658 }
1659
1660 return $value;
1661 }
1662
1679 private function fetchOrRegenerate( $key, $ttl, $callback, array $opts, array $cbParams ) {
1680 $checkKeys = $opts['checkKeys'] ?? [];
1681 $graceTTL = $opts['graceTTL'] ?? self::GRACE_TTL_NONE;
1682 $minAsOf = $opts['minAsOf'] ?? self::MIN_TIMESTAMP_NONE;
1683 $hotTTR = $opts['hotTTR'] ?? self::HOT_TTR;
1684 $lowTTL = $opts['lowTTL'] ?? min( self::LOW_TTL, $ttl );
1685 $ageNew = $opts['ageNew'] ?? self::AGE_NEW;
1686 $touchedCb = $opts['touchedCallback'] ?? null;
1687 $startTime = $this->getCurrentTime();
1688
1689 $keygroup = $this->determineKeyGroupForStats( $key );
1690
1691 // Get the current key value and its metadata
1692 $curState = $this->fetchKeys( [ $key ], $checkKeys, $startTime, $touchedCb )[$key];
1693 $curValue = $curState[self::RES_VALUE];
1694
1695 // Use the cached value if it exists and is not due for synchronous regeneration
1696 if ( $this->isAcceptablyFreshValue( $curState, $graceTTL, $minAsOf ) ) {
1697 if ( !$this->isLotteryRefreshDue( $curState, $lowTTL, $ageNew, $hotTTR, $startTime ) ) {
1698 $this->stats->getTiming( 'wanobjectcache_getwithset_seconds' )
1699 ->setLabel( 'keygroup', $keygroup )
1700 ->setLabel( 'result', 'hit' )
1701 ->setLabel( 'reason', 'good' )
1702 ->observe( 1e3 * ( $this->getCurrentTime() - $startTime ) );
1703
1704 return [ $curValue, $curState[self::RES_VERSION], $curState[self::RES_AS_OF] ];
1705 } elseif ( $this->scheduleAsyncRefresh( $key, $ttl, $callback, $opts, $cbParams ) ) {
1706 $this->logger->debug( "fetchOrRegenerate($key): hit with async refresh" );
1707
1708 $this->stats->getTiming( 'wanobjectcache_getwithset_seconds' )
1709 ->setLabel( 'keygroup', $keygroup )
1710 ->setLabel( 'result', 'hit' )
1711 ->setLabel( 'reason', 'refresh' )
1712 ->observe( 1e3 * ( $this->getCurrentTime() - $startTime ) );
1713
1714 return [ $curValue, $curState[self::RES_VERSION], $curState[self::RES_AS_OF] ];
1715 } else {
1716 $this->logger->debug( "fetchOrRegenerate($key): hit with sync refresh" );
1717 }
1718 }
1719
1720 $isKeyTombstoned = ( $curState[self::RES_TOMB_AS_OF] !== null );
1721 // Use the interim key as a temporary alternative if the key is tombstoned
1722 if ( $isKeyTombstoned ) {
1723 $volState = $this->getInterimValue( $key, $minAsOf, $startTime, $touchedCb );
1724 $volValue = $volState[self::RES_VALUE];
1725 } else {
1726 $volState = $curState;
1727 $volValue = $curValue;
1728 }
1729
1730 // During the volatile "hold-off" period that follows a purge of the key, the value
1731 // will be regenerated many times if frequently accessed. This is done to mitigate
1732 // the effects of backend replication lag as soon as possible. However, throttle the
1733 // overhead of locking and regeneration by reusing values recently written to cache
1734 // tens of milliseconds ago. Verify the "as of" time against the last purge event.
1735 $lastPurgeTime = max(
1736 // RES_TOUCH_AS_OF depends on the value (possibly from the interim key)
1737 $volState[self::RES_TOUCH_AS_OF],
1738 $curState[self::RES_TOMB_AS_OF],
1739 $curState[self::RES_CHECK_AS_OF]
1740 );
1741 $safeMinAsOf = max( $minAsOf, $lastPurgeTime + self::TINY_POSITIVE );
1742
1743 if ( $volState[self::RES_VALUE] === false || $volState[self::RES_AS_OF] < $safeMinAsOf ) {
1744 $isExtremelyNewValue = false;
1745 } else {
1746 $age = $startTime - $volState[self::RES_AS_OF];
1747 $isExtremelyNewValue = ( $age < mt_rand( self::RECENT_SET_LOW_MS, self::RECENT_SET_HIGH_MS ) / 1e3 );
1748 }
1749 if ( $isExtremelyNewValue ) {
1750 $this->logger->debug( "fetchOrRegenerate($key): volatile hit" );
1751
1752 $this->stats->getTiming( 'wanobjectcache_getwithset_seconds' )
1753 ->setLabel( 'keygroup', $keygroup )
1754 ->setLabel( 'result', 'hit' )
1755 ->setLabel( 'reason', 'volatile' )
1756 ->observe( 1e3 * ( $this->getCurrentTime() - $startTime ) );
1757
1758 return [ $volValue, $volState[self::RES_VERSION], $curState[self::RES_AS_OF] ];
1759 }
1760
1761 $lockTSE = $opts['lockTSE'] ?? self::TSE_NONE;
1762 $busyValue = $opts['busyValue'] ?? null;
1763 $staleTTL = $opts['staleTTL'] ?? self::STALE_TTL_NONE;
1764 $segmentable = $opts['segmentable'] ?? false;
1765 $version = $opts['version'] ?? null;
1766
1767 // Determine whether one thread per datacenter should handle regeneration at a time
1768 $useRegenerationLock =
1769 // Note that since tombstones no-op set(), $lockTSE and $curTTL cannot be used to
1770 // deduce the key hotness because |$curTTL| will always keep increasing until the
1771 // tombstone expires or is overwritten by a new tombstone. Also, even if $lockTSE
1772 // is not set, constant regeneration of a key for the tombstone lifetime might be
1773 // very expensive. Assume tombstoned keys are possibly hot in order to reduce
1774 // the risk of high regeneration load after the delete() method is called.
1775 $isKeyTombstoned ||
1776 // Assume a key is hot if requested soon ($lockTSE seconds) after purge.
1777 // This avoids stampedes when timestamps from $checkKeys/$touchedCb bump.
1778 (
1779 $curState[self::RES_CUR_TTL] !== null &&
1780 $curState[self::RES_CUR_TTL] <= 0 &&
1781 abs( $curState[self::RES_CUR_TTL] ) <= $lockTSE
1782 ) ||
1783 // Assume a key is hot if there is no value and a busy fallback is given.
1784 // This avoids stampedes on eviction or preemptive regeneration taking too long.
1785 ( $busyValue !== null && $volValue === false );
1786
1787 // If a regeneration lock is required, threads that do not get the lock will try to use
1788 // the stale value, the interim value, or the $busyValue placeholder, in that order. If
1789 // none of those are set then all threads will bypass the lock and regenerate the value.
1790 $mutexKey = $this->makeSisterKey( $key, self::TYPE_MUTEX );
1791 // Note that locking is not bypassed due to I/O errors; this avoids stampedes
1792 $hasLock = $useRegenerationLock && $this->cache->add( $mutexKey, 1, self::LOCK_TTL );
1793 if ( $useRegenerationLock && !$hasLock ) {
1794 // Determine if there is stale or volatile cached value that is still usable
1795 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable False positive
1796 if ( $this->isValid( $volValue, $volState[self::RES_AS_OF], $minAsOf ) ) {
1797 $this->logger->debug( "fetchOrRegenerate($key): returning stale value" );
1798
1799 $this->stats->getTiming( 'wanobjectcache_getwithset_seconds' )
1800 ->setLabel( 'keygroup', $keygroup )
1801 ->setLabel( 'result', 'hit' )
1802 ->setLabel( 'reason', 'stale' )
1803 ->observe( 1e3 * ( $this->getCurrentTime() - $startTime ) );
1804
1805 return [ $volValue, $volState[self::RES_VERSION], $curState[self::RES_AS_OF] ];
1806 } elseif ( $busyValue !== null ) {
1807 $miss = is_infinite( $minAsOf ) ? 'renew' : 'miss';
1808 $this->logger->debug( "fetchOrRegenerate($key): busy $miss" );
1809
1810 $this->stats->getTiming( 'wanobjectcache_getwithset_seconds' )
1811 ->setLabel( 'keygroup', $keygroup )
1812 ->setLabel( 'result', $miss )
1813 ->setLabel( 'reason', 'busy' )
1814 ->observe( 1e3 * ( $this->getCurrentTime() - $startTime ) );
1815
1816 $placeholderValue = ( $busyValue instanceof Closure ) ? $busyValue() : $busyValue;
1817
1818 return [ $placeholderValue, $version, $curState[self::RES_AS_OF] ];
1819 }
1820 }
1821
1822 // Generate the new value given any prior value with a matching version
1823 $setOpts = [];
1824 $preCallbackTime = $this->getCurrentTime();
1825 ++$this->callbackDepth;
1826 // https://github.com/phan/phan/issues/4419
1828 $value = null;
1829 try {
1830 $value = $callback(
1831 ( $curState[self::RES_VERSION] === $version ) ? $curValue : false,
1832 $ttl,
1833 $setOpts,
1834 ( $curState[self::RES_VERSION] === $version ) ? $curState[self::RES_AS_OF] : null,
1835 $cbParams
1836 );
1837 } finally {
1838 --$this->callbackDepth;
1839 }
1840 $postCallbackTime = $this->getCurrentTime();
1841
1842 // How long it took to generate the value
1843 $walltime = max( $postCallbackTime - $preCallbackTime, 0.0 );
1844
1845 $this->stats->getTiming( 'wanobjectcache_regen_seconds' )
1846 ->setLabel( 'keygroup', $keygroup )
1847 ->observe( 1e3 * $walltime );
1848
1849 // Attempt to save the newly generated value if applicable
1850 if (
1851 // Callback yielded a cacheable value
1852 ( $value !== false && $ttl >= 0 ) &&
1853 // Current thread was not raced out of a regeneration lock or key is tombstoned
1854 ( !$useRegenerationLock || $hasLock || $isKeyTombstoned )
1855 ) {
1856 // If the key is write-holed then use the (volatile) interim key as an alternative
1857 if ( $isKeyTombstoned ) {
1858 $this->setInterimValue(
1859 $key,
1860 $value,
1861 $lockTSE,
1862 $version,
1863 $segmentable
1864 );
1865 } else {
1866 $this->setMainValue(
1867 $key,
1868 $value,
1869 $ttl,
1870 $version,
1871 $walltime,
1872 // @phan-suppress-next-line PhanCoalescingAlwaysNull
1873 $setOpts['lag'] ?? 0,
1874 // @phan-suppress-next-line PhanCoalescingAlwaysNull
1875 $setOpts['since'] ?? $preCallbackTime,
1876 // @phan-suppress-next-line PhanCoalescingAlwaysNull
1877 $setOpts['pending'] ?? false,
1878 $lockTSE,
1879 $staleTTL,
1880 $segmentable,
1881 ( $curValue === false )
1882 );
1883 }
1884 }
1885
1886 if ( $hasLock ) {
1887 $this->cache->delete( $mutexKey, $this->cache::WRITE_BACKGROUND );
1888 }
1889
1890 $miss = is_infinite( $minAsOf ) ? 'renew' : 'miss';
1891 $this->logger->debug( "fetchOrRegenerate($key): $miss, new value computed" );
1892
1893 $this->stats->getTiming( 'wanobjectcache_getwithset_seconds' )
1894 ->setLabel( 'keygroup', $keygroup )
1895 ->setLabel( 'result', $miss )
1896 ->setLabel( 'reason', 'compute' )
1897 ->observe( 1e3 * ( $this->getCurrentTime() - $startTime ) );
1898
1899 return [ $value, $version, $curState[self::RES_AS_OF] ];
1900 }
1901
1911 private function makeSisterKey( string $baseKey, string $typeChar ) {
1912 if ( $this->coalesceScheme === self::SCHEME_HASH_STOP ) {
1913 // Key style: "WANCache:<base key>|#|<character>"
1914 $sisterKey = 'WANCache:' . $baseKey . '|#|' . $typeChar;
1915 } else {
1916 // Key style: "WANCache:{<base key>}:<character>"
1917 $sisterKey = 'WANCache:{' . $baseKey . '}:' . $typeChar;
1918 }
1919 return $sisterKey;
1920 }
1921
1931 private function getInterimValue( $key, $minAsOf, $now, $touchedCb ) {
1932 if ( $this->useInterimHoldOffCaching ) {
1933 $interimSisterKey = $this->makeSisterKey( $key, self::TYPE_INTERIM );
1934 $wrapped = $this->cache->get( $interimSisterKey );
1935 $res = $this->unwrap( $wrapped, $now );
1936 if ( $res[self::RES_VALUE] !== false && $res[self::RES_AS_OF] >= $minAsOf ) {
1937 if ( $touchedCb !== null ) {
1938 // Update "last purge time" since the $touchedCb timestamp depends on $value
1939 // Get the new "touched timestamp", accounting for callback-checked dependencies
1940 $res[self::RES_TOUCH_AS_OF] = max(
1941 $touchedCb( $res[self::RES_VALUE] ),
1942 $res[self::RES_TOUCH_AS_OF]
1943 );
1944 }
1945
1946 return $res;
1947 }
1948 }
1949
1950 return $this->unwrap( false, $now );
1951 }
1952
1961 private function setInterimValue(
1962 $key,
1963 $value,
1964 $ttl,
1965 ?int $version,
1966 bool $segmentable
1967 ) {
1968 $now = $this->getCurrentTime();
1969 $ttl = max( self::INTERIM_KEY_TTL, (int)$ttl );
1970
1971 // Wrap that value with time/TTL/version metadata
1972 $wrapped = $this->wrap( $value, $ttl, $version, $now );
1973
1974 $flags = $this->cache::WRITE_BACKGROUND;
1975 if ( $segmentable ) {
1976 $flags |= $this->cache::WRITE_ALLOW_SEGMENTS;
1977 }
1978
1979 return $this->cache->set(
1980 $this->makeSisterKey( $key, self::TYPE_INTERIM ),
1981 $wrapped,
1982 $ttl,
1983 $flags
1984 );
1985 }
1986
2052 final public function getMultiWithSetCallback(
2053 ArrayIterator $keyedIds, $ttl, callable $callback, array $opts = []
2054 ) {
2055 $span = $this->startOperationSpan( __FUNCTION__, '' );
2056 if ( $span->getContext()->isSampled() ) {
2057 $span->setAttributes( [
2058 'org.wikimedia.wancache.multi_count' => $keyedIds->count(),
2059 'org.wikimedia.wancache.ttl' => $ttl,
2060 ] );
2061 }
2062 // Batch load required keys into the in-process warmup cache
2063 $this->warmupCache = $this->fetchWrappedValuesForWarmupCache(
2064 $this->getNonProcessCachedMultiKeys( $keyedIds, $opts ),
2065 $opts['checkKeys'] ?? []
2066 );
2067 $this->warmupKeyMisses = 0;
2068
2069 // The required callback signature includes $id as the first argument for convenience
2070 // to distinguish different items. To reuse the code in getWithSetCallback(), wrap the
2071 // callback with a proxy callback that has the standard getWithSetCallback() signature.
2072 // This is defined only once per batch to avoid closure creation overhead.
2073 $proxyCb = static function ( $oldValue, &$ttl, &$setOpts, $oldAsOf, $params )
2074 use ( $callback )
2075 {
2076 return $callback( $params['id'], $oldValue, $ttl, $setOpts, $oldAsOf );
2077 };
2078
2079 // Get the order-preserved result map using the warm-up cache
2080 $values = [];
2081 foreach ( $keyedIds as $key => $id ) {
2082 $values[$key] = $this->getWithSetCallback(
2083 $key,
2084 $ttl,
2085 $proxyCb,
2086 $opts,
2087 [ 'id' => $id ]
2088 );
2089 }
2090
2091 $this->warmupCache = [];
2092
2093 return $values;
2094 }
2095
2162 final public function getMultiWithUnionSetCallback(
2163 ArrayIterator $keyedIds, $ttl, callable $callback, array $opts = []
2164 ) {
2165 $span = $this->startOperationSpan( __FUNCTION__, '' );
2166 if ( $span->getContext()->isSampled() ) {
2167 $span->setAttributes( [
2168 'org.wikimedia.wancache.multi_count' => $keyedIds->count(),
2169 'org.wikimedia.wancache.ttl' => $ttl,
2170 ] );
2171 }
2172 $checkKeys = $opts['checkKeys'] ?? []; // TODO: ???
2173 $minAsOf = $opts['minAsOf'] ?? self::MIN_TIMESTAMP_NONE;
2174
2175 // unset incompatible keys
2176 unset( $opts['lockTSE'] );
2177 unset( $opts['busyValue'] );
2178
2179 // Batch load required keys into the in-process warmup cache
2180 $keysByIdGet = $this->getNonProcessCachedMultiKeys( $keyedIds, $opts );
2181 $this->warmupCache = $this->fetchWrappedValuesForWarmupCache( $keysByIdGet, $checkKeys );
2182 $this->warmupKeyMisses = 0;
2183
2184 // IDs of entities known to be in need of generation
2185 $idsRegen = [];
2186
2187 // Find out which keys are missing/deleted/stale
2188 $now = $this->getCurrentTime();
2189 $resByKey = $this->fetchKeys( $keysByIdGet, $checkKeys, $now );
2190 foreach ( $keysByIdGet as $id => $key ) {
2191 $res = $resByKey[$key];
2192 if (
2193 $res[self::RES_VALUE] === false ||
2194 $res[self::RES_CUR_TTL] < 0 ||
2195 $res[self::RES_AS_OF] < $minAsOf
2196 ) {
2197 $idsRegen[] = $id;
2198 }
2199 }
2200
2201 // Run the callback to populate the generation value map for all required IDs
2202 $newSetOpts = [];
2203 $newTTLsById = array_fill_keys( $idsRegen, $ttl );
2204 $newValsById = $idsRegen ? $callback( $idsRegen, $newTTLsById, $newSetOpts ) : [];
2205
2206 $method = __METHOD__;
2207 // The required callback signature includes $id as the first argument for convenience
2208 // to distinguish different items. To reuse the code in getWithSetCallback(), wrap the
2209 // callback with a proxy callback that has the standard getWithSetCallback() signature.
2210 // This is defined only once per batch to avoid closure creation overhead.
2211 $proxyCb = function ( $oldValue, &$ttl, &$setOpts, $oldAsOf, $params )
2212 use ( $callback, $newValsById, $newTTLsById, $newSetOpts, $method )
2213 {
2214 $id = $params['id'];
2215
2216 if ( array_key_exists( $id, $newValsById ) ) {
2217 // Value was already regenerated as expected, so use the value in $newValsById
2218 $newValue = $newValsById[$id];
2219 $ttl = $newTTLsById[$id];
2220 $setOpts = $newSetOpts;
2221 } else {
2222 // Pre-emptive/popularity refresh and version mismatch cases are not detected
2223 // above and thus $newValsById has no entry. Run $callback on this single entity.
2224 $ttls = [ $id => $ttl ];
2225 $result = $callback( [ $id ], $ttls, $setOpts );
2226 if ( !isset( $result[$id] ) ) {
2227 // T303092
2228 $this->logger->warning(
2229 $method . ' failed due to {id} not set in result {result}', [
2230 'id' => $id,
2231 'result' => json_encode( $result )
2232 ] );
2233 }
2234 $newValue = $result[$id];
2235 $ttl = $ttls[$id];
2236 }
2237
2238 return $newValue;
2239 };
2240
2241 // Get the order-preserved result map using the warm-up cache
2242 $values = [];
2243 foreach ( $keyedIds as $key => $id ) {
2244 $values[$key] = $this->getWithSetCallback(
2245 $key,
2246 $ttl,
2247 $proxyCb,
2248 $opts,
2249 [ 'id' => $id ]
2250 );
2251 }
2252
2253 $this->warmupCache = [];
2254
2255 return $values;
2256 }
2257
2265 public function makeGlobalKey( $keygroup, ...$components ) {
2266 return $this->cache->makeGlobalKey( $keygroup, ...$components );
2267 }
2268
2276 public function makeKey( $keygroup, ...$components ) {
2277 return $this->cache->makeKey( $keygroup, ...$components );
2278 }
2279
2321 final public function makeMultiKeys( array $ids, $keyCallback ) {
2322 $idByKey = [];
2323 foreach ( $ids as $id ) {
2324 $key = $keyCallback( $id, $this );
2325 // Edge case: ignore key collisions due to duplicate $ids like "42" and 42
2326 if ( !isset( $idByKey[$key] ) ) {
2327 $idByKey[$key] = $id;
2328 } elseif ( (string)$id !== (string)$idByKey[$key] ) {
2329 throw new UnexpectedValueException(
2330 "Cache key collision; IDs ('$id','{$idByKey[$key]}') map to '$key'"
2331 );
2332 }
2333 }
2334
2335 return new ArrayIterator( $idByKey );
2336 }
2337
2373 final public function multiRemap( array $ids, array $res ) {
2374 if ( count( $ids ) !== count( $res ) ) {
2375 // If makeMultiKeys() is called on a list of non-unique IDs, then the resulting
2376 // ArrayIterator will have less entries due to "first appearance" de-duplication
2377 $ids = array_keys( array_fill_keys( $ids, true ) );
2378 if ( count( $ids ) !== count( $res ) ) {
2379 throw new UnexpectedValueException( "Multi-key result does not match ID list" );
2380 }
2381 }
2382
2383 return array_combine( $ids, $res );
2384 }
2385
2392 public function watchErrors() {
2393 return $this->cache->watchErrors();
2394 }
2395
2413 final public function getLastError( $watchPoint = 0 ) {
2414 $code = $this->cache->getLastError( $watchPoint );
2415 switch ( $code ) {
2416 case BagOStuff::ERR_NONE:
2417 return BagOStuff::ERR_NONE;
2418 case BagOStuff::ERR_NO_RESPONSE:
2419 return BagOStuff::ERR_NO_RESPONSE;
2420 case BagOStuff::ERR_UNREACHABLE:
2421 return BagOStuff::ERR_UNREACHABLE;
2422 default:
2423 return BagOStuff::ERR_UNEXPECTED;
2424 }
2425 }
2426
2432 public function clearProcessCache() {
2433 $this->processCaches = [];
2434 }
2435
2456 final public function useInterimHoldOffCaching( $enabled ) {
2457 $this->useInterimHoldOffCaching = $enabled;
2458 }
2459
2465 public function getQoS( $flag ) {
2466 return $this->cache->getQoS( $flag );
2467 }
2468
2532 public function adaptiveTTL( $mtime, $maxTTL, $minTTL = 30, $factor = 0.2 ) {
2533 // handle fractional seconds and string integers
2534 $mtime = (int)$mtime;
2535 if ( $mtime <= 0 ) {
2536 // no last-modified time provided
2537 return $minTTL;
2538 }
2539
2540 $age = (int)$this->getCurrentTime() - $mtime;
2541
2542 return (int)min( $maxTTL, max( $minTTL, $factor * $age ) );
2543 }
2544
2550 final public function getWarmupKeyMisses() {
2551 // Number of misses in $this->warmupCache during the last call to certain methods
2552 return $this->warmupKeyMisses;
2553 }
2554
2559 protected function getRouteKey( string $sisterKey ) {
2560 if ( $this->broadcastRoute !== null ) {
2561 if ( $sisterKey[0] === '/' ) {
2562 throw new RuntimeException( "Sister key '$sisterKey' already contains a route." );
2563 }
2564 return $this->broadcastRoute . $sisterKey;
2565 }
2566 return $sisterKey;
2567 }
2568
2580 private function scheduleAsyncRefresh( $key, $ttl, $callback, array $opts, array $cbParams ) {
2581 if ( !$this->asyncHandler ) {
2582 return false;
2583 }
2584 // Update the cache value later, such during post-send of an HTTP request. This forces
2585 // cache regeneration by setting "minAsOf" to infinity, meaning that no existing value
2586 // is considered valid. Furthermore, note that preemptive regeneration is not applicable
2587 // to invalid values, so there is no risk of infinite preemptive regeneration loops.
2588 $func = $this->asyncHandler;
2589 $func( function () use ( $key, $ttl, $callback, $opts, $cbParams ) {
2590 $opts['minAsOf'] = INF;
2591 try {
2592 $this->fetchOrRegenerate( $key, $ttl, $callback, $opts, $cbParams );
2593 } catch ( Exception $e ) {
2594 // Log some context for easier debugging
2595 $this->logger->error( 'Async refresh failed for {key}', [
2596 'key' => $key,
2597 'ttl' => $ttl,
2598 'exception' => $e
2599 ] );
2600 throw $e;
2601 }
2602 } );
2603
2604 return true;
2605 }
2606
2615 private function isAcceptablyFreshValue( $res, $graceTTL, $minAsOf ) {
2616 if ( !$this->isValid( $res[self::RES_VALUE], $res[self::RES_AS_OF], $minAsOf ) ) {
2617 // Value does not exists or is too old
2618 return false;
2619 }
2620
2621 $curTTL = $res[self::RES_CUR_TTL];
2622 if ( $curTTL > 0 ) {
2623 // Value is definitely still fresh
2624 return true;
2625 }
2626
2627 // Remaining seconds during which this stale value can be used
2628 $curGraceTTL = $graceTTL + $curTTL;
2629
2630 return ( $curGraceTTL > 0 )
2631 // Chance of using the value decreases as $curTTL goes from 0 to -$graceTTL
2632 ? !$this->worthRefreshExpiring( $curGraceTTL, $graceTTL, $graceTTL )
2633 // Value is too stale to fall in the grace period
2634 : false;
2635 }
2636
2647 protected function isLotteryRefreshDue( $res, $lowTTL, $ageNew, $hotTTR, $now ) {
2648 $curTTL = $res[self::RES_CUR_TTL];
2649 $logicalTTL = $res[self::RES_TTL];
2650 $asOf = $res[self::RES_AS_OF];
2651
2652 return (
2653 $this->worthRefreshExpiring( $curTTL, $logicalTTL, $lowTTL ) ||
2654 $this->worthRefreshPopular( $asOf, $ageNew, $hotTTR, $now )
2655 );
2656 }
2657
2695 protected function worthRefreshPopular( $asOf, $ageNew, $hotTTR, $now ) {
2696 if ( $ageNew < 0 || $hotTTR <= 0 ) {
2697 return false;
2698 }
2699
2700 $age = $now - $asOf;
2701 $timeOld = $age - $ageNew;
2702 if ( $timeOld <= 0 ) {
2703 return false;
2704 }
2705
2706 $popularHitsPerSec = 1;
2707 // Lifecycle is: new, ramp-up refresh chance, full refresh chance.
2708 // Note that the "expected # of refreshes" for the ramp-up time range is half
2709 // of what it would be if P(refresh) was at its full value during that time range.
2710 $refreshWindowSec = max( $hotTTR - $ageNew - self::RAMPUP_TTL / 2, 1 );
2711 // P(refresh) * (# hits in $refreshWindowSec) = (expected # of refreshes)
2712 // P(refresh) * ($refreshWindowSec * $popularHitsPerSec) = 1 (by definition)
2713 // P(refresh) = 1/($refreshWindowSec * $popularHitsPerSec)
2714 $chance = 1 / ( $popularHitsPerSec * $refreshWindowSec );
2715 // Ramp up $chance from 0 to its nominal value over RAMPUP_TTL seconds to avoid stampedes
2716 $chance *= ( $timeOld <= self::RAMPUP_TTL ) ? $timeOld / self::RAMPUP_TTL : 1;
2717
2718 return ( mt_rand( 1, 1_000_000_000 ) <= 1_000_000_000 * $chance );
2719 }
2720
2757 protected function worthRefreshExpiring( $curTTL, $logicalTTL, $lowTTL ) {
2758 if ( $lowTTL <= 0 ) {
2759 return false;
2760 }
2761 // T264787: avoid having keys start off with a high chance of being refreshed;
2762 // the point where refreshing becomes possible cannot precede the key lifetime.
2763 $effectiveLowTTL = min( $lowTTL, $logicalTTL ?: INF );
2764
2765 // How long the value was in the "low TTL" phase
2766 $timeOld = $effectiveLowTTL - $curTTL;
2767 if ( $timeOld <= 0 || $timeOld >= $effectiveLowTTL ) {
2768 return false;
2769 }
2770
2771 // Ratio of the low TTL phase that has elapsed (r)
2772 $ttrRatio = $timeOld / $effectiveLowTTL;
2773 // Use p(r) as the monotonically increasing "chance of refresh" function,
2774 // having p(0)=0 and p(1)=1. The value expires at the nominal expiry.
2775 $chance = $ttrRatio ** 4;
2776
2777 return ( mt_rand( 1, 1_000_000_000 ) <= 1_000_000_000 * $chance );
2778 }
2779
2788 protected function isValid( $value, $asOf, $minAsOf ) {
2789 return ( $value !== false && $asOf >= $minAsOf );
2790 }
2791
2799 private function wrap( $value, $ttl, $version, $now ) {
2800 // Returns keys in ascending integer order for PHP7 array packing:
2801 // https://nikic.github.io/2014/12/22/PHPs-new-hashtable-implementation.html
2802 $wrapped = [
2803 self::FLD_FORMAT_VERSION => self::VERSION,
2804 self::FLD_VALUE => $value,
2805 self::FLD_TTL => $ttl,
2806 self::FLD_TIME => $now
2807 ];
2808 if ( $version !== null ) {
2809 $wrapped[self::FLD_VALUE_VERSION] = $version;
2810 }
2811
2812 return $wrapped;
2813 }
2814
2829 private function unwrap( $wrapped, $now ) {
2830 // https://nikic.github.io/2014/12/22/PHPs-new-hashtable-implementation.html
2831 $res = [
2832 // Attributes that only depend on the fetched key value
2833 self::RES_VALUE => false,
2834 self::RES_VERSION => null,
2835 self::RES_AS_OF => null,
2836 self::RES_TTL => null,
2837 self::RES_TOMB_AS_OF => null,
2838 // Attributes that depend on caller-specific "check" keys or "touched callbacks"
2839 self::RES_CHECK_AS_OF => null,
2840 self::RES_TOUCH_AS_OF => null,
2841 self::RES_CUR_TTL => null
2842 ];
2843
2844 if ( is_array( $wrapped ) ) {
2845 // Entry expected to be a cached value; validate it
2846 if (
2847 ( $wrapped[self::FLD_FORMAT_VERSION] ?? null ) === self::VERSION &&
2848 $wrapped[self::FLD_TIME] >= $this->epoch
2849 ) {
2850 if ( $wrapped[self::FLD_TTL] > 0 ) {
2851 // Get the approximate time left on the key
2852 $age = $now - $wrapped[self::FLD_TIME];
2853 $curTTL = max( $wrapped[self::FLD_TTL] - $age, 0.0 );
2854 } else {
2855 // Key had no TTL, so the time left is unbounded
2856 $curTTL = INF;
2857 }
2858 $res[self::RES_VALUE] = $wrapped[self::FLD_VALUE];
2859 $res[self::RES_VERSION] = $wrapped[self::FLD_VALUE_VERSION] ?? null;
2860 $res[self::RES_AS_OF] = $wrapped[self::FLD_TIME];
2861 $res[self::RES_CUR_TTL] = $curTTL;
2862 $res[self::RES_TTL] = $wrapped[self::FLD_TTL];
2863 }
2864 } else {
2865 // Entry expected to be a tombstone; parse it
2866 $purge = $this->parsePurgeValue( $wrapped );
2867 if ( $purge !== null ) {
2868 // Tombstoned keys should always have a negative "current TTL"
2869 $curTTL = min( $purge[self::PURGE_TIME] - $now, self::TINY_NEGATIVE );
2870 $res[self::RES_CUR_TTL] = $curTTL;
2871 $res[self::RES_TOMB_AS_OF] = $purge[self::PURGE_TIME];
2872 }
2873 }
2874
2875 return $res;
2876 }
2877
2883 private function determineKeyGroupForStats( $key ) {
2884 $parts = explode( ':', $key, 3 );
2885 // Fallback in case the key was not made by makeKey.
2886 // Replace dots because they are special in StatsD (T232907)
2887 return strtr( $parts[1] ?? $parts[0], '.', '_' );
2888 }
2889
2898 private function parsePurgeValue( $value ) {
2899 if ( !is_string( $value ) ) {
2900 return null;
2901 }
2902
2903 $segments = explode( ':', $value, 3 );
2904 $prefix = $segments[0];
2905 if ( $prefix !== self::PURGE_VAL_PREFIX ) {
2906 // Not a purge value
2907 return null;
2908 }
2909
2910 $timestamp = (float)$segments[1];
2911 // makeTombstonePurgeValue() doesn't store hold-off TTLs
2912 $holdoff = isset( $segments[2] ) ? (int)$segments[2] : self::HOLDOFF_TTL;
2913
2914 if ( $timestamp < $this->epoch ) {
2915 // Purge value is too old
2916 return null;
2917 }
2918
2919 return [ self::PURGE_TIME => $timestamp, self::PURGE_HOLDOFF => $holdoff ];
2920 }
2921
2928 private function makeCheckPurgeValue( float $timestamp, int $holdoff, ?array &$purge = null ) {
2929 $normalizedTime = (int)$timestamp;
2930 // Purge array that matches what parsePurgeValue() would have returned
2931 $purge = [ self::PURGE_TIME => (float)$normalizedTime, self::PURGE_HOLDOFF => $holdoff ];
2932
2933 return self::PURGE_VAL_PREFIX . ":$normalizedTime:$holdoff";
2934 }
2935
2940 private function getProcessCache( $group ) {
2941 if ( !isset( $this->processCaches[$group] ) ) {
2942 [ , $size ] = explode( ':', $group );
2943 $this->processCaches[$group] = new MapCacheLRU( (int)$size );
2944 if ( $this->wallClockOverride !== null ) {
2945 $this->processCaches[$group]->setMockTime( $this->wallClockOverride );
2946 }
2947 }
2948
2949 return $this->processCaches[$group];
2950 }
2951
2957 private function getNonProcessCachedMultiKeys( ArrayIterator $keys, array $opts ) {
2958 $pcTTL = $opts['pcTTL'] ?? self::TTL_UNCACHEABLE;
2959
2960 $keysMissing = [];
2961 if ( $pcTTL > 0 && $this->callbackDepth == 0 ) {
2962 $pCache = $this->getProcessCache( $opts['pcGroup'] ?? self::PC_PRIMARY );
2963 foreach ( $keys as $key => $id ) {
2964 if ( !$pCache->has( $key, $pcTTL ) ) {
2965 $keysMissing[$id] = $key;
2966 }
2967 }
2968 }
2969
2970 return $keysMissing;
2971 }
2972
2979 private function fetchWrappedValuesForWarmupCache( array $keys, array $checkKeys ) {
2980 if ( !$keys ) {
2981 return [];
2982 }
2983
2984 // Get all the value keys to fetch...
2985 $sisterKeys = [];
2986 foreach ( $keys as $baseKey ) {
2987 $sisterKeys[] = $this->makeSisterKey( $baseKey, self::TYPE_VALUE );
2988 }
2989 // Get all the "check" keys to fetch...
2990 foreach ( $checkKeys as $i => $checkKeyOrKeyGroup ) {
2991 // Note: avoid array_merge() inside loop in case there are many keys
2992 if ( is_int( $i ) ) {
2993 // Single "check" key that applies to all value keys
2994 $sisterKeys[] = $this->makeSisterKey( $checkKeyOrKeyGroup, self::TYPE_TIMESTAMP );
2995 } else {
2996 // List of "check" keys that apply to a specific value key
2997 foreach ( (array)$checkKeyOrKeyGroup as $checkKey ) {
2998 $sisterKeys[] = $this->makeSisterKey( $checkKey, self::TYPE_TIMESTAMP );
2999 }
3000 }
3001 }
3002
3003 $wrappedBySisterKey = $this->cache->getMulti( $sisterKeys );
3004 $wrappedBySisterKey += array_fill_keys( $sisterKeys, false );
3005
3006 return $wrappedBySisterKey;
3007 }
3008
3014 private function timeSinceLoggedMiss( $key, $now ) {
3015 // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.Found
3016 for ( end( $this->missLog ); $miss = current( $this->missLog ); prev( $this->missLog ) ) {
3017 if ( $miss[0] === $key ) {
3018 return ( $now - $miss[1] );
3019 }
3020 }
3021
3022 return null;
3023 }
3024
3029 protected function getCurrentTime() {
3030 return $this->wallClockOverride ?: microtime( true );
3031 }
3032
3037 public function setMockTime( &$time ) {
3038 $this->wallClockOverride =& $time;
3039 $this->cache->setMockTime( $time );
3040 foreach ( $this->processCaches as $pCache ) {
3041 $pCache->setMockTime( $time );
3042 }
3043 }
3044
3056 private function startOperationSpan( $opName, $keys, $checkKeys = [] ) {
3057 $span = $this->tracer->createSpan( "WANObjectCache::$opName" )
3058 ->setSpanKind( SpanInterface::SPAN_KIND_CLIENT )
3059 ->start();
3060
3061 if ( !$span->getContext()->isSampled() ) {
3062 return $span;
3063 }
3064
3065 $keys = is_array( $keys ) ? implode( ' ', $keys ) : $keys;
3066
3067 if ( count( $checkKeys ) > 0 ) {
3068 $checkKeys = array_map(
3069 static fn ( $checkKeyOrKeyGroup ) =>
3070 is_array( $checkKeyOrKeyGroup )
3071 ? implode( ' ', $checkKeyOrKeyGroup )
3072 : $checkKeyOrKeyGroup,
3073 $checkKeys );
3074
3075 $checkKeys = implode( ' ', $checkKeys );
3076 $span->setAttributes( [ 'org.wikimedia.wancache.check_keys' => $checkKeys ] );
3077 }
3078
3079 $span->setAttributes( [ 'org.wikimedia.wancache.keys' => $keys ] );
3080
3081 $span->activate();
3082 return $span;
3083 }
3084}
3085
3087class_alias( WANObjectCache::class, 'WANObjectCache' );
Abstract class for any ephemeral data store.
Definition BagOStuff.php:73
No-op implementation that stores nothing.
Store key-value entries in a size-limited in-memory LRU cache.
Multi-datacenter aware caching interface.
makeMultiKeys(array $ids, $keyCallback)
Get an iterator of (cache key => entity ID) for a list of entity IDs.
adaptiveTTL( $mtime, $maxTTL, $minTTL=30, $factor=0.2)
Get a TTL that is higher for objects that have not changed recently.
watchErrors()
Get a "watch point" token that can be used to get the "last error" to occur after now.
bool $useInterimHoldOffCaching
Whether to use "interim" caching while keys are tombstoned.
static newEmpty()
Get an instance that wraps EmptyBagOStuff.
const HOLDOFF_TTL_NONE
Idiom for delete()/touchCheckKey() meaning "no hold-off period".
float $epoch
Unix timestamp of the oldest possible valid values.
const KEY_VERSION
Version number attribute for a key; keep value for b/c (< 1.36)
fetchKeys(array $keys, array $checkKeys, float $now, $touchedCb=null)
Fetch the value and key metadata of several keys from cache.
isLotteryRefreshDue( $res, $lowTTL, $ageNew, $hotTTR, $now)
Check if a key is due for randomized regeneration due to near-expiration/popularity.
resetCheckKey( $key)
Clear the last-purge timestamp of a "check" key in all datacenters.
int $coalesceScheme
Scheme to use for key coalescing (Hash Tags or Hash Stops)
worthRefreshExpiring( $curTTL, $logicalTTL, $lowTTL)
Check if a key is nearing expiration and thus due for randomized regeneration.
makeGlobalKey( $keygroup,... $components)
const STALE_TTL_NONE
Idiom for set()/getWithSetCallback() meaning "no post-expiration persistence".
isValid( $value, $asOf, $minAsOf)
Check that a wrapper value exists and has an acceptable age.
const TTL_LAGGED
Max TTL, in seconds, to store keys when a data source has high replication lag.
getMultiCheckKeyTime(array $keys)
Fetch the values of each timestamp "check" key.
getWithSetCallback( $key, $ttl, $callback, array $opts=[], array $cbParams=[])
Method to fetch/regenerate a cache key.
getMultiWithSetCallback(ArrayIterator $keyedIds, $ttl, callable $callback, array $opts=[])
Method to fetch multiple cache keys at once with regeneration.
const KEY_CUR_TTL
Remaining TTL attribute for a key; keep value for b/c (< 1.36)
BagOStuff $cache
The local datacenter cache.
const HOLDOFF_TTL
Seconds to tombstone keys on delete() and to treat keys as volatile after purges.
string null $broadcastRoute
Routing prefix for operations that should be broadcasted to all data centers.
touchCheckKey( $key, $holdoff=self::HOLDOFF_TTL)
Increase the last-purge timestamp of a "check" key in all datacenters.
getLastError( $watchPoint=0)
Get the "last error" registry.
const KEY_TTL
Logical TTL attribute for a key.
const KEY_AS_OF
Generation completion timestamp attribute for a key; keep value for b/c (< 1.36)
const KEY_CHECK_AS_OF
Highest "check" key timestamp for a key; keep value for b/c (< 1.36)
callable null $asyncHandler
Function that takes a WAN cache callback and runs it later.
getCheckKeyTime( $key)
Fetch the value of a timestamp "check" key.
const KEY_TOMB_AS_OF
Tombstone timestamp attribute for a key; keep value for b/c (< 1.36)
MapCacheLRU[] $processCaches
Map of group PHP instance caches.
makeKey( $keygroup,... $components)
getMulti(array $keys, &$curTTLs=[], array $checkKeys=[], &$info=[])
Fetch the value of several keys from cache.
getMultiWithUnionSetCallback(ArrayIterator $keyedIds, $ttl, callable $callback, array $opts=[])
Method to fetch/regenerate multiple cache keys at once.
const PASS_BY_REF
Idiom for get()/getMulti() to return extra information by reference.
useInterimHoldOffCaching( $enabled)
Enable or disable the use of brief caching for tombstoned keys.
clearProcessCache()
Clear the in-process caches; useful for testing.
worthRefreshPopular( $asOf, $ageNew, $hotTTR, $now)
Check if a key is due for randomized regeneration due to its popularity.
multiRemap(array $ids, array $res)
Get an (ID => value) map from (i) a non-unique list of entity IDs, and (ii) the list of corresponding...
const GRACE_TTL_NONE
Idiom for set()/getWithSetCallback() meaning "no post-expiration grace period".
This is the primary interface for validating metrics definitions, caching defined metrics,...
A no-op tracer that creates no-op spans and persists no data.
return[ 'config-schema-inverse'=>['default'=>['ConfigRegistry'=>['main'=> 'MediaWiki\\Config\\GlobalVarConfig::newInstance',], 'Sitename'=> 'MediaWiki', 'Server'=> false, 'CanonicalServer'=> false, 'ServerName'=> false, 'AssumeProxiesUseDefaultProtocolPorts'=> true, 'HttpsPort'=> 443, 'ForceHTTPS'=> false, 'ScriptPath'=> '/wiki', 'UsePathInfo'=> null, 'Script'=> false, 'LoadScript'=> false, 'RestPath'=> false, 'StylePath'=> false, 'LocalStylePath'=> false, 'ExtensionAssetsPath'=> false, 'ExtensionDirectory'=> null, 'StyleDirectory'=> null, 'ArticlePath'=> false, 'UploadPath'=> false, 'ImgAuthPath'=> false, 'ThumbPath'=> false, 'UploadDirectory'=> false, 'FileCacheDirectory'=> false, 'Logo'=> false, 'Logos'=> false, 'Favicon'=> '/favicon.ico', 'AppleTouchIcon'=> false, 'ReferrerPolicy'=> false, 'TmpDirectory'=> false, 'UploadBaseUrl'=> '', 'UploadStashScalerBaseUrl'=> false, 'ActionPaths'=>[], 'MainPageIsDomainRoot'=> false, 'EnableUploads'=> false, 'UploadStashMaxAge'=> 21600, 'EnableAsyncUploads'=> false, 'EnableAsyncUploadsByURL'=> false, 'UploadMaintenance'=> false, 'IllegalFileChars'=> ':\\/\\\\', 'DeletedDirectory'=> false, 'ImgAuthDetails'=> false, 'ImgAuthUrlPathMap'=>[], 'LocalFileRepo'=>['class'=> 'MediaWiki\\FileRepo\\LocalRepo', 'name'=> 'local', 'directory'=> null, 'scriptDirUrl'=> null, 'favicon'=> null, 'url'=> null, 'hashLevels'=> null, 'thumbScriptUrl'=> null, 'transformVia404'=> null, 'deletedDir'=> null, 'deletedHashLevels'=> null, 'updateCompatibleMetadata'=> null, 'reserializeMetadata'=> null,], 'ForeignFileRepos'=>[], 'UseInstantCommons'=> false, 'UseSharedUploads'=> false, 'SharedUploadDirectory'=> null, 'SharedUploadPath'=> null, 'HashedSharedUploadDirectory'=> true, 'RepositoryBaseUrl'=> 'https:'FetchCommonsDescriptions'=> false, 'SharedUploadDBname'=> false, 'SharedUploadDBprefix'=> '', 'CacheSharedUploads'=> true, 'ForeignUploadTargets'=>['local',], 'UploadDialog'=>['fields'=>['description'=> true, 'date'=> false, 'categories'=> false,], 'licensemessages'=>['local'=> 'generic-local', 'foreign'=> 'generic-foreign',], 'comment'=>['local'=> '', 'foreign'=> '',], 'format'=>['filepage'=> ' $DESCRIPTION', 'description'=> ' $TEXT', 'ownwork'=> '', 'license'=> '', 'uncategorized'=> '',],], 'FileBackends'=>[], 'LockManagers'=>[], 'ShowEXIF'=> null, 'UpdateCompatibleMetadata'=> false, 'AllowCopyUploads'=> false, 'CopyUploadsDomains'=>[], 'CopyUploadsFromSpecialUpload'=> false, 'CopyUploadProxy'=> false, 'CopyUploadTimeout'=> false, 'CopyUploadAllowOnWikiDomainConfig'=> false, 'MaxUploadSize'=> 104857600, 'MinUploadChunkSize'=> 1024, 'UploadNavigationUrl'=> false, 'UploadMissingFileUrl'=> false, 'ThumbnailScriptPath'=> false, 'SharedThumbnailScriptPath'=> false, 'HashedUploadDirectory'=> true, 'CSPUploadEntryPoint'=> true, 'FileExtensions'=>['png', 'gif', 'jpg', 'jpeg', 'webp',], 'ProhibitedFileExtensions'=>['html', 'htm', 'js', 'jsb', 'mhtml', 'mht', 'xhtml', 'xht', 'php', 'phtml', 'php3', 'php4', 'php5', 'phps', 'phar', 'shtml', 'jhtml', 'pl', 'py', 'cgi', 'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl', 'xml',], 'MimeTypeExclusions'=>['text/html', 'application/javascript', 'text/javascript', 'text/x-javascript', 'application/x-shellscript', 'application/x-php', 'text/x-php', 'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh', 'text/scriptlet', 'application/x-msdownload', 'application/x-msmetafile', 'application/java', 'application/xml', 'text/xml',], 'CheckFileExtensions'=> true, 'StrictFileExtensions'=> true, 'DisableUploadScriptChecks'=> false, 'UploadSizeWarning'=> false, 'TrustedMediaFormats'=>['BITMAP', 'AUDIO', 'VIDEO', 'image/svg+xml', 'application/pdf',], 'MediaHandlers'=>[], 'NativeImageLazyLoading'=> false, 'ParserTestMediaHandlers'=>['image/jpeg'=> 'MockBitmapHandler', 'image/png'=> 'MockBitmapHandler', 'image/gif'=> 'MockBitmapHandler', 'image/tiff'=> 'MockBitmapHandler', 'image/webp'=> 'MockBitmapHandler', 'image/x-ms-bmp'=> 'MockBitmapHandler', 'image/x-bmp'=> 'MockBitmapHandler', 'image/x-xcf'=> 'MockBitmapHandler', 'image/svg+xml'=> 'MockSvgHandler', 'image/vnd.djvu'=> 'MockDjVuHandler',], 'UseImageResize'=> true, 'UseImageMagick'=> false, 'ImageMagickConvertCommand'=> '/usr/bin/convert', 'MaxInterlacingAreas'=>[], 'SharpenParameter'=> '0x0.4', 'SharpenReductionThreshold'=> 0.85, 'ImageMagickTempDir'=> false, 'CustomConvertCommand'=> false, 'JpegTran'=> '/usr/bin/jpegtran', 'JpegPixelFormat'=> 'yuv420', 'JpegQuality'=> 80, 'Exiv2Command'=> '/usr/bin/exiv2', 'Exiftool'=> '/usr/bin/exiftool', 'SVGConverters'=>['ImageMagick'=> ' $path/convert -background "#ffffff00" -thumbnail $widthx$height\\! $input PNG:$output', 'inkscape'=> ' $path/inkscape -w $width -o $output $input', 'batik'=> 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input', 'rsvg'=> ' $path/rsvg-convert -w $width -h $height -o $output $input', 'ImagickExt'=>['SvgHandler::rasterizeImagickExt',],], 'SVGConverter'=> 'ImageMagick', 'SVGConverterPath'=> '', 'SVGMaxSize'=> 5120, 'SVGMetadataCutoff'=> 5242880, 'SVGNativeRendering'=> true, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'TiffThumbnailType'=>[], 'ThumbnailEpoch'=> '20030516000000', 'AttemptFailureEpoch'=> 1, 'IgnoreImageErrors'=> false, 'GenerateThumbnailOnParse'=> true, 'ShowArchiveThumbnails'=> true, 'EnableAutoRotation'=> null, 'Antivirus'=> null, 'AntivirusSetup'=>['clamav'=>['command'=> 'clamscan --no-summary ', 'codemap'=>[0=> 0, 1=> 1, 52=> -1, ' *'=> false,], 'messagepattern'=> '/.*?:(.*)/sim',],], 'AntivirusRequired'=> true, 'VerifyMimeType'=> true, 'MimeTypeFile'=> 'internal', 'MimeInfoFile'=> 'internal', 'MimeDetectorCommand'=> null, 'TrivialMimeDetection'=> false, 'XMLMimeTypes'=>['http:'svg'=> 'image/svg+xml', 'http:'http:'html'=> 'text/html',], 'ImageLimits'=>[[320, 240,], [640, 480,], [800, 600,], [1024, 768,], [1280, 1024,], [2560, 2048,],], 'ThumbLimits'=>[120, 150, 180, 200, 220, 250, 300, 400,], 'ThumbnailNamespaces'=>[6,], 'ThumbnailSteps'=> null, 'ThumbnailBuckets'=> null, 'ThumbnailMinimumBucketDistance'=> 50, 'UploadThumbnailRenderMap'=>[], 'UploadThumbnailRenderMethod'=> 'jobqueue', 'UploadThumbnailRenderHttpCustomHost'=> false, 'UploadThumbnailRenderHttpCustomDomain'=> false, 'UseTinyRGBForJPGThumbnails'=> false, 'GalleryOptions'=>[], 'ThumbUpright'=> 0.75, 'DirectoryMode'=> 511, 'ResponsiveImages'=> true, 'ImagePreconnect'=> false, 'TrackMediaRequestProvenance'=> false, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, 'EmailConfirmationBanner'=> false, 'EnotifWatchlist'=> false, 'EnotifUserTalk'=> false, 'EnotifRevealEditorAddress'=> false, 'EnotifMinorEdits'=> true, 'EnotifUseRealName'=> false, 'UsersNotifiedOnAllChanges'=>[], 'DBname'=> 'my_wiki', 'DBmwschema'=> null, 'DBprefix'=> '', 'DBserver'=> 'localhost', 'DBport'=> 5432, 'DBuser'=> 'wikiuser', 'DBpassword'=> '', 'DBtype'=> 'mysql', 'DBssl'=> false, 'DBcompress'=> false, 'DBStrictWarnings'=> false, 'DBadminuser'=> null, 'DBadminpassword'=> null, 'SearchType'=> null, 'SearchTypeAlternatives'=> null, 'DBTableOptions'=> 'ENGINE=InnoDB, DEFAULT CHARSET=binary', 'SQLMode'=> '', 'SQLiteDataDir'=> '', 'SharedDB'=> null, 'SharedPrefix'=> false, 'SharedTables'=>['user', 'user_properties', 'user_autocreate_serial',], 'SharedSchema'=> false, 'DBservers'=> false, 'LBFactoryConf'=>['class'=> 'Wikimedia\\Rdbms\\LBFactorySimple',], 'DataCenterUpdateStickTTL'=> 10, 'DBerrorLog'=> false, 'DBerrorLogTZ'=> false, 'LocalDatabases'=>[], 'DatabaseReplicaLagWarning'=> 10, 'DatabaseReplicaLagCritical'=> 30, 'MaxExecutionTimeForExpensiveQueries'=> 0, 'VirtualDomainsMapping'=>[], 'FileSchemaMigrationStage'=> 3, 'ExternalLinksDomainGaps'=>[], 'ContentHandlers'=>['wikitext'=>['class'=> 'MediaWiki\\Content\\WikitextContentHandler', 'services'=>['TitleFactory', 'ParserFactory', 'GlobalIdGenerator', 'LanguageNameUtils', 'LinkRenderer', 'MagicWordFactory', 'ParsoidParserFactory',],], 'javascript'=>['class'=> 'MediaWiki\\Content\\JavaScriptContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup', 'CodeHighlighter',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup', 'CodeHighlighter',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'CodeHighlighter',],], 'text'=> 'MediaWiki\\Content\\TextContentHandler', 'unknown'=> 'MediaWiki\\Content\\FallbackContentHandler',], 'NamespaceContentModels'=>[], 'TextModelsToParse'=>['wikitext', 'javascript', 'css',], 'CompressRevisions'=> false, 'ExternalStores'=>[], 'ExternalServers'=>[], 'DefaultExternalStore'=> false, 'RevisionCacheExpiry'=> 604800, 'PageLanguageUseDB'=> false, 'DiffEngine'=> null, 'ExternalDiffEngine'=> false, 'Wikidiff2Options'=>[], 'RequestTimeLimit'=> null, 'TransactionalTimeLimit'=> 120, 'CriticalSectionTimeLimit'=> 180.0, 'MiserMode'=> false, 'DisableQueryPages'=> false, 'QueryCacheLimit'=> 1000, 'WantedPagesThreshold'=> 1, 'AllowSlowParserFunctions'=> false, 'AllowSchemaUpdates'=> true, 'MaxArticleSize'=> 2048, 'MemoryLimit'=> '50M', 'PoolCounterConf'=> null, 'PoolCountClientConf'=>['servers'=>['127.0.0.1',], 'timeout'=> 0.1,], 'MaxUserDBWriteDuration'=> false, 'MaxJobDBWriteDuration'=> false, 'LinkHolderBatchSize'=> 1000, 'MaximumMovedPages'=> 100, 'ForceDeferredUpdatesPreSend'=> false, 'MultiShardSiteStats'=> false, 'CacheDirectory'=> false, 'MainCacheType'=> 0, 'MessageCacheType'=> -1, 'ParserCacheType'=> -1, 'SessionCacheType'=> -1, 'AnonSessionCacheType'=> false, 'LanguageConverterCacheType'=> -1, 'ObjectCaches'=>[0=>['class'=> 'Wikimedia\\ObjectCache\\EmptyBagOStuff', 'reportDupes'=> false,], 1=>['class'=> 'MediaWiki\\ObjectCache\\SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400, 'WarmParsoidParserCache'=> false,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'PHPSessionHandling'=> 'warn', 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, 'JwtSessionCookieIssuer'=> null, 'MemCachedServers'=>['127.0.0.1:11211',], 'MemCachedPersistent'=> false, 'MemCachedTimeout'=> 500000, 'UseLocalMessageCache'=> false, 'AdaptiveMessageCache'=> false, 'LocalisationCacheConf'=>['class'=> 'MediaWiki\\Language\\LocalisationCache', 'store'=> 'detect', 'storeClass'=> false, 'storeDirectory'=> false, 'storeServer'=>[], 'forceRecache'=> false, 'manualRecache'=> false,], 'CachePages'=> true, 'CacheEpoch'=> '20030516000000', 'GitInfoCacheDirectory'=> false, 'UseFileCache'=> false, 'FileCacheDepth'=> 2, 'RenderHashAppend'=> '', 'EnableSidebarCache'=> false, 'SidebarCacheExpiry'=> 86400, 'UseGzip'=> false, 'InvalidateCacheOnLocalSettingsChange'=> true, 'ExtensionInfoMTime'=> false, 'EnableRemoteBagOStuffTests'=> false, 'UseCdn'=> false, 'VaryOnXFP'=> false, 'InternalServer'=> false, 'CdnMaxAge'=> 18000, 'CdnMaxageLagged'=> 30, 'CdnMaxageStale'=> 10, 'CdnReboundPurgeDelay'=> 0, 'CdnMaxageSubstitute'=> 60, 'ForcedRawSMaxage'=> 300, 'CdnServers'=>[], 'CdnServersNoPurge'=>[], 'HTCPRouting'=>[], 'HTCPMulticastTTL'=> 1, 'UsePrivateIPs'=> false, 'CdnMatchParameterOrder'=> true, 'LanguageCode'=> 'en', 'GrammarForms'=>[], 'InterwikiMagic'=> true, 'HideInterlanguageLinks'=> false, 'ExtraInterlanguageLinkPrefixes'=>[], 'InterlanguageLinkCodeMap'=>[], 'ExtraLanguageNames'=>[], 'ExtraLanguageCodes'=>['bh'=> 'bho', 'no'=> 'nb', 'simple'=> 'en',], 'DummyLanguageCodes'=>[], 'AllUnicodeFixes'=> false, 'LegacyEncoding'=> false, 'AmericanDates'=> false, 'TranslateNumerals'=> true, 'UseDatabaseMessages'=> true, 'MaxMsgCacheEntrySize'=> 10000, 'DisableLangConversion'=> false, 'DisableTitleConversion'=> false, 'DefaultLanguageVariant'=> false, 'UsePigLatinVariant'=> false, 'DisabledVariants'=>[], 'VariantArticlePath'=> false, 'UseXssLanguage'=> false, 'LoginLanguageSelector'=> false, 'ForceUIMsgAsContentMsg'=>[], 'RawHtmlMessages'=>[], 'Localtimezone'=> null, 'LocalTZoffset'=> null, 'OverrideUcfirstCharacters'=>[], 'MimeType'=> 'text/html', 'Html5Version'=> null, 'EditSubmitButtonLabelPublish'=> false, 'XhtmlNamespaces'=>[], 'SiteNotice'=> '', 'BrowserFormatDetection'=> 'telephone=no', 'SkinMetaTags'=>[], 'DefaultSkin'=> 'vector-2022', 'FallbackSkin'=> 'fallback', 'SkipSkins'=>[], 'DisableOutputCompression'=> false, 'FragmentMode'=>['html5', 'legacy',], 'ExternalInterwikiFragmentMode'=> 'legacy', 'FooterIcons'=>['copyright'=>['copyright'=>[],], 'poweredby'=>['mediawiki'=>['src'=> null, 'url'=> 'https:'alt'=> 'Powered by MediaWiki', 'lang'=> 'en',],],], 'UseCombinedLoginLink'=> false, 'Edititis'=> false, 'Send404Code'=> true, 'ShowRollbackEditCount'=> 10, 'EnableCanonicalServerLink'=> false, 'InterwikiLogoOverride'=>[], 'ResourceModules'=>[], 'ResourceModuleSkinStyles'=>[], 'ResourceLoaderSources'=>[], 'ResourceBasePath'=> null, 'ResourceLoaderMaxage'=>[], 'ResourceLoaderDebug'=> false, 'ResourceLoaderMaxQueryLength'=> false, 'ResourceLoaderValidateJS'=> true, 'ResourceLoaderEnableJSProfiler'=> false, 'ResourceLoaderStorageEnabled'=> true, 'ResourceLoaderStorageVersion'=> 1, 'ResourceLoaderEnableSourceMapLinks'=> true, 'AllowSiteCSSOnRestrictedPages'=> false, 'VueDevelopmentMode'=> false, 'CodexDevelopmentDir'=> null, 'MetaNamespace'=> false, 'MetaNamespaceTalk'=> false, 'CanonicalNamespaceNames'=>[-2=> 'Media', -1=> 'Special', 0=> '', 1=> 'Talk', 2=> 'User', 3=> 'User_talk', 4=> 'Project', 5=> 'Project_talk', 6=> 'File', 7=> 'File_talk', 8=> 'MediaWiki', 9=> 'MediaWiki_talk', 10=> 'Template', 11=> 'Template_talk', 12=> 'Help', 13=> 'Help_talk', 14=> 'Category', 15=> 'Category_talk',], 'ExtraNamespaces'=>[], 'ExtraGenderNamespaces'=>[], 'NamespaceAliases'=>[], 'LegalTitleChars'=> ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+', 'CapitalLinks' => true, 'CapitalLinkOverrides' => [ ], 'NamespacesWithSubpages' => [ 1 => true, 2 => true, 3 => true, 4 => true, 5 => true, 7 => true, 8 => true, 9 => true, 10 => true, 11 => true, 12 => true, 13 => true, 15 => true, ], 'NamespacesWithoutAutoSummaries' => [ ], 'ContentNamespaces' => [ 0, ], 'ShortPagesNamespaceExclusions' => [ ], 'ExtraSignatureNamespaces' => [ ], 'InvalidRedirectTargets' => [ 'Filepath', 'Mypage', 'Mytalk', 'Redirect', 'Mylog', ], 'DisableHardRedirects' => false, 'FixDoubleRedirects' => false, 'LocalInterwikis' => [ ], 'InterwikiExpiry' => 10800, 'InterwikiCache' => false, 'InterwikiScopes' => 3, 'InterwikiFallbackSite' => 'wiki', 'RedirectSources' => false, 'SiteTypes' => [ 'mediawiki' => 'MediaWiki\\Site\\MediaWikiSite', ], 'MaxTocLevel' => 999, 'MaxPPNodeCount' => 1000000, 'MaxTemplateDepth' => 100, 'MaxPPExpandDepth' => 100, 'UrlProtocols' => [ 'bitcoin:', 'ftp: 'ftps: 'geo:', 'git: 'gopher: 'http: 'https: 'irc: 'ircs: 'magnet:', 'mailto:', 'matrix:', 'mms: 'news:', 'nntp: 'redis: 'sftp: 'sip:', 'sips:', 'sms:', 'ssh: 'svn: 'tel:', 'telnet: 'urn:', 'wikipedia: 'worldwind: 'xmpp:', ' ], 'CleanSignatures' => true, 'AllowExternalImages' => false, 'AllowExternalImagesFrom' => '', 'EnableImageWhitelist' => false, 'TidyConfig' => [ ], 'ParsoidSettings' => [ 'useSelser' => true, ], 'ParsoidExperimentalParserFunctionOutput' => false, 'RawHtml' => false, 'ExternalLinkTarget' => false, 'NoFollowLinks' => true, 'NoFollowNsExceptions' => [ ], 'NoFollowDomainExceptions' => [ 'mediawiki.org', ], 'RegisterInternalExternals' => false, 'ExternalLinksIgnoreDomains' => [ ], 'AllowDisplayTitle' => true, 'RestrictDisplayTitle' => true, 'ExpensiveParserFunctionLimit' => 100, 'PreprocessorCacheThreshold' => 1000, 'EnableScaryTranscluding' => false, 'TranscludeCacheExpiry' => 3600, 'EnableMagicLinks' => [ 'ISBN' => false, 'PMID' => false, 'RFC' => false, ], 'ParserEnableUserLanguage' => false, 'ArticleCountMethod' => 'link', 'ActiveUserDays' => 30, 'LearnerEdits' => 10, 'LearnerMemberSince' => 4, 'ExperiencedUserEdits' => 500, 'ExperiencedUserMemberSince' => 30, 'ManualRevertSearchRadius' => 15, 'RevertedTagMaxDepth' => 15, 'CentralIdLookupProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\CentralId\\LocalIdLookup', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', 'HideUserUtils', ], ], ], 'CentralIdLookupProvider' => 'local', 'UserRegistrationProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\Registration\\LocalUserRegistrationProvider', 'services' => [ 'ConnectionProvider', ], ], ], 'PasswordPolicy' => [ 'policies' => [ 'bureaucrat' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'sysop' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'interface-admin' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'bot' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'default' => [ 'MinimalPasswordLength' => [ 'value' => 8, 'suggestChangeOnLogin' => true, ], 'PasswordCannotBeSubstringInUsername' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'PasswordCannotMatchDefaults' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'MaximalPasswordLength' => [ 'value' => 4096, 'suggestChangeOnLogin' => true, ], 'PasswordNotInCommonList' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], ], ], 'checks' => [ 'MinimalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimalPasswordLength', ], 'MinimumPasswordLengthToLogin' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimumPasswordLengthToLogin', ], 'PasswordCannotBeSubstringInUsername' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotBeSubstringInUsername', ], 'PasswordCannotMatchDefaults' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotMatchDefaults', ], 'MaximalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMaximalPasswordLength', ], 'PasswordNotInCommonList' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordNotInCommonList', ], ], ], 'AuthManagerConfig' => null, 'AuthManagerAutoConfig' => [ 'preauth' => [ 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider', 'services' => [ 'ConnectionProvider', 'UserFactory', ], 'sort' => 0, ], ], 'primaryauth' => [ 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', 'UserOptionsLookup', ], 'args' => [ [ 'authoritative' => false, ], ], 'sort' => 0, ], 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'args' => [ [ 'authoritative' => true, ], ], 'sort' => 100, ], ], 'secondaryauth' => [ 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider', 'sort' => 100, ], 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'sort' => 200, ], ], ], 'RememberMe' => 'choose', 'ReauthenticateTime' => [ 'default' => 3600, ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'default' => true, ], 'ChangeCredentialsBlacklist' => [ 'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest', ], 'RemoveCredentialsBlacklist' => [ 'MediaWiki\\Auth\\PasswordAuthenticationRequest', ], 'InvalidPasswordReset' => true, 'PasswordDefault' => 'pbkdf2', 'PasswordConfig' => [ 'A' => [ 'class' => 'MediaWiki\\Password\\MWOldPassword', ], 'B' => [ 'class' => 'MediaWiki\\Password\\MWSaltedPassword', ], 'pbkdf2-legacyA' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'A', 'pbkdf2', ], ], 'pbkdf2-legacyB' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'B', 'pbkdf2', ], ], 'bcrypt' => [ 'class' => 'MediaWiki\\Password\\BcryptPassword', 'cost' => 9, ], 'pbkdf2' => [ 'class' => 'MediaWiki\\Password\\Pbkdf2PasswordUsingOpenSSL', 'algo' => 'sha512', 'cost' => '30000', 'length' => '64', ], 'argon2' => [ 'class' => 'MediaWiki\\Password\\Argon2Password', 'algo' => 'auto', ], ], 'PasswordResetRoutes' => [ 'username' => true, 'email' => true, ], 'MaxSigChars' => 255, 'SignatureValidation' => 'warning', 'SignatureAllowedLintErrors' => [ 'obsolete-tag', ], 'MaxNameChars' => 255, 'ReservedUsernames' => [ 'MediaWiki default', 'Conversion script', 'Maintenance script', 'Template namespace initialisation script', 'ScriptImporter', 'Delete page script', 'Move page script', 'Command line script', 'Unknown user', 'msg:double-redirect-fixer', 'msg:usermessage-editor', 'msg:proxyblocker', 'msg:sorbs', 'msg:spambot_username', 'msg:autochange-username', ], 'DefaultUserOptions' => [ 'ccmeonemails' => 0, 'date' => 'default', 'diffonly' => 0, 'diff-type' => 'table', 'disablemail' => 0, 'editfont' => 'monospace', 'editondblclick' => 0, 'editrecovery' => 0, 'editsectiononrightclick' => 0, 'email-allow-new-users' => 1, 'enotifminoredits' => 0, 'enotifrevealaddr' => 0, 'enotifusertalkpages' => 1, 'enotifwatchlistpages' => 1, 'extendwatchlist' => 1, 'fancysig' => 0, 'forceeditsummary' => 0, 'forcesafemode' => 0, 'gender' => 'unknown', 'hidecategorization' => 1, 'hideminor' => 0, 'hidepatrolled' => 0, 'imagesize' => 2, 'minordefault' => 0, 'newpageshidepatrolled' => 0, 'nickname' => '', 'norollbackdiff' => 0, 'prefershttps' => 1, 'previewonfirst' => 0, 'previewontop' => 1, 'pst-cssjs' => 1, 'rcdays' => 7, 'rcenhancedfilters-disable' => 0, 'rclimit' => 50, 'requireemail' => 0, 'search-match-redirect' => true, 'search-special-page' => 'Search', 'search-thumbnail-extra-namespaces' => true, 'searchlimit' => 20, 'showhiddencats' => 0, 'shownumberswatching' => 1, 'showrollbackconfirmation' => 0, 'skin' => false, 'skin-responsive' => 1, 'thumbsize' => 5, 'underline' => 2, 'useeditwarning' => 1, 'uselivepreview' => 0, 'usenewrc' => 1, 'watchcreations' => 1, 'watchcreations-expiry' => 'infinite', 'watchdefault' => 1, 'watchdefault-expiry' => 'infinite', 'watchdeletion' => 0, 'watchlistdays' => 7, 'watchlisthideanons' => 0, 'watchlisthidebots' => 0, 'watchlisthidecategorization' => 1, 'watchlisthideliu' => 0, 'watchlisthideminor' => 0, 'watchlisthideown' => 0, 'watchlisthidepatrolled' => 0, 'watchlistreloadautomatically' => 0, 'watchlistunwatchlinks' => 0, 'watchmoves' => 0, 'watchrollback' => 0, 'watchuploads' => 1, 'watchrollback-expiry' => 'infinite', 'watchstar-expiry' => 'infinite', 'wlenhancedfilters-disable' => 0, 'wllimit' => 250, ], 'ConditionalUserOptions' => [ ], 'HiddenPrefs' => [ ], 'UserJsPrefLimit' => 100, 'InvalidUsernameCharacters' => '@:>=', 'UserrightsInterwikiDelimiter' => '@', 'SecureLogin' => false, 'AuthenticationTokenVersion' => null, 'SessionProviders' => [ 'MediaWiki\\Session\\CookieSessionProvider' => [ 'class' => 'MediaWiki\\Session\\CookieSessionProvider', 'args' => [ [ 'priority' => 30, ], ], 'services' => [ 'JwtCodec', 'UrlUtils', ], ], 'MediaWiki\\Session\\BotPasswordSessionProvider' => [ 'class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', 'args' => [ [ 'priority' => 75, ], ], 'services' => [ 'GrantsInfo', ], ], ], 'AutoCreateTempUser' => [ 'known' => false, 'enabled' => false, 'actions' => [ 'edit', ], 'genPattern' => '~$1', 'matchPattern' => null, 'reservedPattern' => '~$1', 'serialProvider' => [ 'type' => 'local', 'useYear' => true, ], 'serialMapping' => [ 'type' => 'readable-numeric', ], 'expireAfterDays' => 90, 'notifyBeforeExpirationDays' => 10, ], 'AutoblockExemptions' => [ ], 'AutoblockExpiry' => 86400, 'BlockAllowsUTEdit' => true, 'BlockCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 19, ], 'BlockDisablesLogin' => false, 'EnableMultiBlocks' => false, 'WhitelistRead' => false, 'WhitelistReadRegexp' => false, 'EmailConfirmToEdit' => false, 'HideIdentifiableRedirects' => true, 'GroupPermissions' => [ '*' => [ 'createaccount' => true, 'autocreateaccount' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'viewmyprivateinfo' => true, 'editmyprivateinfo' => true, 'editmyoptions' => true, ], 'user' => [ 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'movefile' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'minoredit' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, 'editmyuserjsredirect' => true, 'sendemail' => true, 'applychangetags' => true, 'changetags' => true, 'viewmywatchlist' => true, 'editmywatchlist' => true, 'createwithcontentmodel' => true, 'logout' => true, ], 'autoconfirmed' => [ 'autoconfirmed' => true, 'editsemiprotected' => true, ], 'bot' => [ 'bot' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'nominornewtalk' => true, 'autopatrol' => true, 'suppressredirect' => true, 'apihighlimits' => true, ], 'sysop' => [ 'block' => true, 'createaccount' => true, 'createpreviouslyrenamedaccount' => true, 'delete' => true, 'bigdelete' => true, 'deletedhistory' => true, 'deletedtext' => true, 'undelete' => true, 'editcontentmodel' => true, 'editinterface' => true, 'editsitejson' => true, 'edituserjson' => true, 'import' => true, 'importupload' => true, 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'patrol' => true, 'autopatrol' => true, 'protect' => true, 'editprotected' => true, 'rollback' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'unwatchedpages' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'blockemail' => true, 'markbotedits' => true, 'apihighlimits' => true, 'browsearchive' => true, 'noratelimit' => true, 'movefile' => true, 'unblockself' => true, 'suppressredirect' => true, 'mergehistory' => true, 'managechangetags' => true, 'deletechangetags' => true, ], 'interface-admin' => [ 'editinterface' => true, 'editsitecss' => true, 'editsitejson' => true, 'editsitejs' => true, 'editusercss' => true, 'edituserjson' => true, 'edituserjs' => true, ], 'bureaucrat' => [ 'userrights' => true, 'noratelimit' => true, 'renameuser' => true, ], 'suppress' => [ 'hideuser' => true, 'suppressrevision' => true, 'viewsuppressed' => true, 'suppressionlog' => true, 'deleterevision' => true, 'deletelogentry' => true, ], ], 'PrivilegedGroups' => [ 'bureaucrat', 'interface-admin', 'suppress', 'sysop', ], 'RevokePermissions' => [ ], 'GroupInheritsPermissions' => [ ], 'ImplicitGroups' => [ '*', 'user', 'autoconfirmed', ], 'GroupsAddToSelf' => [ ], 'GroupsRemoveFromSelf' => [ ], 'RestrictedGroups' => [ ], 'UserRequirementsPrivateConditions' => [ ], 'RestrictionTypes' => [ 'create', 'edit', 'move', 'upload', ], 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop', ], 'CascadingRestrictionLevels' => [ 'sysop', ], 'SemiprotectedRestrictionLevels' => [ 'autoconfirmed', ], 'NamespaceProtection' => [ ], 'NonincludableNamespaces' => [ ], 'AutoConfirmAge' => 0, 'AutoConfirmCount' => 0, 'Autopromote' => [ 'autoconfirmed' => [ '&', [ 1, null, ], [ 2, null, ], ], ], 'AutopromoteOnce' => [ 'onEdit' => [ ], ], 'AutopromoteOnceLogInRC' => true, 'AutopromoteOnceRCExcludedGroups' => [ ], 'AddGroups' => [ ], 'RemoveGroups' => [ ], 'AvailableRights' => [ ], 'ImplicitRights' => [ ], 'DeleteRevisionsLimit' => 0, 'DeleteRevisionsBatchSize' => 1000, 'HideUserContribLimit' => 1000, 'AccountCreationThrottle' => [ [ 'count' => 0, 'seconds' => 86400, ], ], 'TempAccountCreationThrottle' => [ [ 'count' => 1, 'seconds' => 600, ], [ 'count' => 6, 'seconds' => 86400, ], ], 'TempAccountNameAcquisitionThrottle' => [ [ 'count' => 60, 'seconds' => 86400, ], ], 'SpamRegex' => [ ], 'SummarySpamRegex' => [ ], 'EnableDnsBlacklist' => false, 'DnsBlacklistUrls' => [ ], 'ProxyList' => [ ], 'ProxyWhitelist' => [ ], 'SoftBlockRanges' => [ ], 'ApplyIpBlocksToXff' => false, 'RateLimits' => [ 'edit' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], 'user' => [ 90, 60, ], ], 'move' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], 'upload' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'rollback' => [ 'user' => [ 10, 60, ], 'newbie' => [ 5, 120, ], ], 'mailpassword' => [ 'ip' => [ 5, 3600, ], ], 'sendemail' => [ 'ip' => [ 5, 86400, ], 'newbie' => [ 5, 86400, ], 'user' => [ 20, 86400, ], ], 'changeemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'confirmemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'purge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'linkpurge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'renderfile' => [ 'ip' => [ 700, 30, ], 'user' => [ 700, 30, ], ], 'renderfile-nonstandard' => [ 'ip' => [ 70, 30, ], 'user' => [ 70, 30, ], ], 'stashedit' => [ 'ip' => [ 30, 60, ], 'newbie' => [ 30, 60, ], ], 'stashbasehtml' => [ 'ip' => [ 5, 60, ], 'newbie' => [ 5, 60, ], ], 'changetags' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'editcontentmodel' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], ], 'RateLimitsExcludedIPs' => [ ], 'PutIPinRC' => true, 'QueryPageDefaultLimit' => 50, 'ExternalQuerySources' => [ ], 'PasswordAttemptThrottle' => [ [ 'count' => 5, 'seconds' => 300, ], [ 'count' => 150, 'seconds' => 172800, ], ], 'GrantPermissions' => [ 'basic' => [ 'autocreateaccount' => true, 'autoconfirmed' => true, 'autopatrol' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'nominornewtalk' => true, 'patrolmarks' => true, 'read' => true, 'unwatchedpages' => true, ], 'highvolume' => [ 'bot' => true, 'apihighlimits' => true, 'noratelimit' => true, 'markbotedits' => true, ], 'import' => [ 'import' => true, 'importupload' => true, ], 'editpage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'pagelang' => true, ], 'editprotected' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, ], 'editmycssjs' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, ], 'editmyoptions' => [ 'editmyoptions' => true, 'editmyuserjson' => true, ], 'editinterface' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, ], 'editsiteconfig' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, 'editusercss' => true, 'edituserjs' => true, 'editsitecss' => true, 'editsitejs' => true, ], 'createeditmovepage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'createpage' => true, 'createtalk' => true, 'delete-redirect' => true, 'move' => true, 'move-rootuserpages' => true, 'move-subpages' => true, 'move-categorypages' => true, 'suppressredirect' => true, ], 'uploadfile' => [ 'upload' => true, 'reupload-own' => true, ], 'uploadeditmovefile' => [ 'upload' => true, 'reupload-own' => true, 'reupload' => true, 'reupload-shared' => true, 'upload_by_url' => true, 'movefile' => true, 'suppressredirect' => true, ], 'patrol' => [ 'patrol' => true, ], 'rollback' => [ 'rollback' => true, ], 'blockusers' => [ 'block' => true, 'blockemail' => true, ], 'viewdeleted' => [ 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, ], 'viewrestrictedlogs' => [ 'suppressionlog' => true, ], 'delete' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, 'delete' => true, 'bigdelete' => true, 'deletelogentry' => true, 'deleterevision' => true, 'undelete' => true, ], 'oversight' => [ 'suppressrevision' => true, 'viewsuppressed' => true, ], 'protect' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, 'protect' => true, ], 'viewmywatchlist' => [ 'viewmywatchlist' => true, ], 'editmywatchlist' => [ 'editmywatchlist' => true, ], 'sendemail' => [ 'sendemail' => true, ], 'createaccount' => [ 'createaccount' => true, ], 'privateinfo' => [ 'viewmyprivateinfo' => true, ], 'mergehistory' => [ 'mergehistory' => true, ], 'managesessions' => [ 'logout' => true, ], ], 'GrantPermissionGroups' => [ 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', 'editprotected' => 'page-interaction', 'patrol' => 'page-interaction', 'uploadfile' => 'file-interaction', 'uploadeditmovefile' => 'file-interaction', 'sendemail' => 'email', 'viewmywatchlist' => 'watchlist-interaction', 'editviewmywatchlist' => 'watchlist-interaction', 'editmycssjs' => 'customization', 'editmyoptions' => 'customization', 'editinterface' => 'administration', 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', 'viewdeleted' => 'administration', 'viewrestrictedlogs' => 'administration', 'protect' => 'administration', 'oversight' => 'administration', 'createaccount' => 'administration', 'mergehistory' => 'administration', 'import' => 'administration', 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', 'managesessions' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editviewmywatchlist' => 'low', 'editmycssjs' => 'security', 'editmyoptions' => 'security', 'editinterface' => 'vandalism', 'editsiteconfig' => 'security', 'rollback' => 'low', 'blockusers' => 'vandalism', 'delete' => 'vandalism', 'viewdeleted' => 'vandalism', 'viewrestrictedlogs' => 'security', 'protect' => 'vandalism', 'oversight' => 'security', 'createaccount' => 'low', 'mergehistory' => 'vandalism', 'import' => 'security', 'highvolume' => 'low', 'privateinfo' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'BotPasswordsLimit' => 100, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPUseReportURIDirective' => false, 'CSPFalsePositiveUrls' => [ 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'chrome-extension' => true, ], 'AllowCrossOrigin' => false, 'RestAllowCrossOriginCookieAuth' => false, 'SessionSecret' => false, 'CookieExpiration' => 2592000, 'ExtendedLoginCookieExpiration' => 15552000, 'SessionCookieJwtExpiration' => 14400, 'CookieDomain' => '', 'CookiePath' => '/', 'CookieSecure' => 'detect', 'CookiePrefix' => false, 'CookieHttpOnly' => true, 'CookieSameSite' => null, 'CacheVaryCookies' => [ ], 'SessionName' => false, 'CookieSetOnAutoblock' => true, 'CookieSetOnIpBlock' => true, 'DebugLogFile' => '', 'DebugLogPrefix' => '', 'DebugRedirects' => false, 'DebugRawPage' => false, 'DebugComments' => false, 'DebugDumpSql' => false, 'TrxProfilerLimits' => [ 'GET' => [ 'masterConns' => 0, 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'POST-nonwrite' => [ 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'PostSend-GET' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 10000, 'maxAffected' => 1000, 'masterConns' => 0, 'writes' => 0, ], 'PostSend-POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'JobRunner' => [ 'readQueryTime' => 30, 'writeQueryTime' => 5, 'readQueryRows' => 100000, 'maxAffected' => 500, ], 'Maintenance' => [ 'writeQueryTime' => 5, 'maxAffected' => 1000, ], ], 'DebugLogGroups' => [ ], 'MWLoggerDefaultSpi' => [ 'class' => 'MediaWiki\\Logger\\LegacySpi', ], 'ShowDebug' => false, 'SpecialVersionShowHooks' => false, 'ShowExceptionDetails' => false, 'LogExceptionBacktrace' => true, 'PropagateErrors' => true, 'ShowHostnames' => false, 'OverrideHostname' => false, 'DevelopmentWarnings' => false, 'DeprecationReleaseLimit' => false, 'Profiler' => [ ], 'StatsdServer' => false, 'StatsdMetricPrefix' => 'MediaWiki', 'StatsTarget' => null, 'StatsFormat' => null, 'StatsPrefix' => 'mediawiki', 'OpenTelemetryConfig' => null, 'PageInfoTransclusionLimit' => 50, 'EnableJavaScriptTest' => false, 'CachePrefix' => false, 'DebugToolbar' => false, 'ApiClientErrorSampleRate' => 1.0, 'DisableTextSearch' => false, 'AdvancedSearchHighlighting' => false, 'SearchHighlightBoundaries' => '[\\p{Z}\\p{P}\\p{C}]', 'OpenSearchTemplates' => [ 'application/x-suggestions+json' => false, 'application/x-suggestions+xml' => false, ], 'OpenSearchDefaultLimit' => 10, 'OpenSearchDescriptionLength' => 100, 'SearchSuggestCacheExpiry' => 1200, 'DisableSearchUpdate' => false, 'NamespacesToBeSearchedDefault' => [ true, ], 'DisableInternalSearch' => false, 'SearchForwardUrl' => null, 'SitemapNamespaces' => false, 'SitemapNamespacesPriorities' => false, 'SitemapApiConfig' => [ ], 'SpecialSearchFormOptions' => [ ], 'SearchMatchRedirectPreference' => false, 'SearchRunSuggestedQuery' => true, 'Diff3' => '/usr/bin/diff3', 'Diff' => '/usr/bin/diff', 'PreviewOnOpenNamespaces' => [ 14 => true, ], 'UniversalEditButton' => true, 'UseAutomaticEditSummaries' => true, 'CommandLineDarkBg' => false, 'ReadOnly' => null, 'ReadOnlyWatchedItemStore' => false, 'ReadOnlyFile' => false, 'UpgradeKey' => false, 'GitBin' => '/usr/bin/git', 'GitRepositoryViewers' => [ 'https: 'ssh: 'https: 'git@github\\.com:(.*?)(\\.git)?' => 'https: ], 'InstallerInitialPages' => [ [ 'titlemsg' => 'mainpage', 'text' => '{{subst:int:mainpagetext}}{{subst:int:mainpagedocfooter}}', ], ], 'RCMaxAge' => 7776000, 'WatchersMaxAge' => 15552000, 'UnwatchedPageSecret' => 1, 'RCFilterByAge' => false, 'RCLinkLimits' => [ 50, 100, 250, 500, ], 'RCLinkDays' => [ 1, 3, 7, 14, 30, ], 'RCFeeds' => [ ], 'RCWatchCategoryMembership' => false, 'UseRCPatrol' => true, 'StructuredChangeFiltersLiveUpdatePollingRate' => 3, 'UseNPPatrol' => true, 'UseFilePatrol' => true, 'Feed' => true, 'FeedLimit' => 50, 'FeedCacheTimeout' => 60, 'FeedDiffCutoff' => 32768, 'OverrideSiteFeed' => [ ], 'FeedClasses' => [ 'rss' => 'MediaWiki\\Feed\\RSSFeed', 'atom' => 'MediaWiki\\Feed\\AtomFeed', ], 'AdvertisedFeedTypes' => [ 'atom', ], 'RCShowWatchingUsers' => false, 'RCShowChangedSize' => true, 'RCChangedSizeThreshold' => 500, 'ShowUpdatedMarker' => true, 'DisableAnonTalk' => false, 'UseTagFilter' => true, 'SoftwareTags' => [ 'mw-contentmodelchange' => true, 'mw-new-redirect' => true, 'mw-removed-redirect' => true, 'mw-changed-redirect-target' => true, 'mw-blank' => true, 'mw-replace' => true, 'mw-recreated' => true, 'mw-rollback' => true, 'mw-undo' => true, 'mw-manual-revert' => true, 'mw-reverted' => true, 'mw-server-side-upload' => true, 'mw-ipblock-appeal' => true, 'mw-edited-other-users-js' => true, 'mw-edited-other-users-css' => true, ], 'UnwatchedPageThreshold' => false, 'RecentChangesFlags' => [ 'newpage' => [ 'letter' => 'newpageletter', 'title' => 'recentchanges-label-newpage', 'legend' => 'recentchanges-legend-newpage', 'grouping' => 'any', ], 'minor' => [ 'letter' => 'minoreditletter', 'title' => 'recentchanges-label-minor', 'legend' => 'recentchanges-legend-minor', 'class' => 'minoredit', 'grouping' => 'all', ], 'bot' => [ 'letter' => 'boteditletter', 'title' => 'recentchanges-label-bot', 'legend' => 'recentchanges-legend-bot', 'class' => 'botedit', 'grouping' => 'all', ], 'unpatrolled' => [ 'letter' => 'unpatrolledletter', 'title' => 'recentchanges-label-unpatrolled', 'legend' => 'recentchanges-legend-unpatrolled', 'grouping' => 'any', ], ], 'WatchlistExpiry' => false, 'EnableWatchstarPopover' => false, 'EnableWatchlistLabels' => false, 'WatchlistLabelsMaxPerUser' => 100, 'WatchlistPurgeRate' => 0.1, 'WatchlistExpiryMaxDuration' => '1 year', 'EnableChangesListQueryPartitioning' => false, 'RightsPage' => null, 'RightsUrl' => null, 'RightsText' => null, 'RightsIcon' => null, 'UseCopyrightUpload' => false, 'MaxCredits' => 0, 'ShowCreditsIfMax' => true, 'ImportSources' => [ ], 'ImportTargetNamespace' => null, 'ExportAllowHistory' => true, 'ExportMaxHistory' => 0, 'ExportAllowListContributors' => false, 'ExportMaxLinkDepth' => 0, 'ExportFromNamespaces' => false, 'ExportAllowAll' => false, 'ExportPagelistLimit' => 5000, 'XmlDumpSchemaVersion' => '0.11', 'WikiFarmSettingsDirectory' => null, 'WikiFarmSettingsExtension' => 'yaml', 'ExtensionFunctions' => [ ], 'ExtensionMessagesFiles' => [ ], 'MessagesDirs' => [ ], 'TranslationAliasesDirs' => [ ], 'ExtensionEntryPointListFiles' => [ ], 'EnableParserLimitReporting' => true, 'ValidSkinNames' => [ ], 'SpecialPages' => [ ], 'ExtensionCredits' => [ ], 'Hooks' => [ ], 'ServiceWiringFiles' => [ ], 'JobClasses' => [ 'deletePage' => 'MediaWiki\\Page\\DeletePageJob', 'refreshLinks' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'deleteLinks' => 'MediaWiki\\Page\\DeleteLinksJob', 'htmlCacheUpdate' => 'MediaWiki\\JobQueue\\Jobs\\HTMLCacheUpdateJob', 'sendMail' => [ 'class' => 'MediaWiki\\Mail\\EmaillingJob', 'services' => [ 'Emailer', ], ], 'enotifNotify' => [ 'class' => 'MediaWiki\\RecentChanges\\RecentChangeNotifyJob', 'services' => [ 'RecentChangeLookup', ], ], 'fixDoubleRedirect' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\DoubleRedirectJob', 'services' => [ 'RevisionLookup', 'MagicWordFactory', 'WikiPageFactory', ], 'needsPage' => true, ], 'AssembleUploadChunks' => 'MediaWiki\\JobQueue\\Jobs\\AssembleUploadChunksJob', 'PublishStashedFile' => 'MediaWiki\\JobQueue\\Jobs\\PublishStashedFileJob', 'ThumbnailRender' => 'MediaWiki\\JobQueue\\Jobs\\ThumbnailRenderJob', 'UploadFromUrl' => 'MediaWiki\\JobQueue\\Jobs\\UploadFromUrlJob', 'recentChangesUpdate' => 'MediaWiki\\RecentChanges\\RecentChangesUpdateJob', 'refreshLinksPrioritized' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'refreshLinksDynamic' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'activityUpdateJob' => 'MediaWiki\\Watchlist\\ActivityUpdateJob', 'categoryMembershipChange' => [ 'class' => 'MediaWiki\\RecentChanges\\CategoryMembershipChangeJob', 'services' => [ 'RecentChangeFactory', ], ], 'CategoryCountUpdateJob' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryCountUpdateJob', 'services' => [ 'ConnectionProvider', 'NamespaceInfo', ], ], 'clearUserWatchlist' => 'MediaWiki\\Watchlist\\ClearUserWatchlistJob', 'watchlistExpiry' => 'MediaWiki\\Watchlist\\WatchlistExpiryJob', 'cdnPurge' => 'MediaWiki\\JobQueue\\Jobs\\CdnPurgeJob', 'userGroupExpiry' => 'MediaWiki\\User\\UserGroupExpiryJob', 'clearWatchlistNotifications' => 'MediaWiki\\Watchlist\\ClearWatchlistNotificationsJob', 'userOptionsUpdate' => 'MediaWiki\\User\\Options\\UserOptionsUpdateJob', 'revertedTagUpdate' => 'MediaWiki\\JobQueue\\Jobs\\RevertedTagUpdateJob', 'null' => 'MediaWiki\\JobQueue\\Jobs\\NullJob', 'userEditCountInit' => 'MediaWiki\\User\\UserEditCountInitJob', 'parsoidCachePrewarm' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\ParsoidCachePrewarmJob', 'services' => [ 'ParserOutputAccess', 'PageStore', 'RevisionLookup', 'ParsoidSiteConfig', ], 'needsPage' => false, ], 'renameUserTable' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], 'renameUserDerived' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserDerivedJob', 'services' => [ 'RenameUserFactory', 'UserFactory', ], ], 'renameUser' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], ], 'JobTypesExcludedFromDefaultQueue' => [ 'AssembleUploadChunks', 'PublishStashedFile', 'UploadFromUrl', ], 'JobBackoffThrottling' => [ ], 'JobTypeConf' => [ 'default' => [ 'class' => 'MediaWiki\\JobQueue\\JobQueueDB', 'order' => 'random', 'claimTTL' => 3600, ], ], 'JobQueueIncludeInMaxLagFactor' => false, 'SpecialPageCacheUpdates' => [ 'Statistics' => [ 'MediaWiki\\Deferred\\SiteStatsUpdate', 'cacheUpdate', ], ], 'PagePropLinkInvalidations' => [ 'hiddencat' => 'categorylinks', ], 'CategoryMagicGallery' => true, 'CategoryPagingLimit' => 200, 'CategoryCollation' => 'uppercase', 'TempCategoryCollations' => [ ], 'SortedCategories' => false, 'TrackingCategories' => [ ], 'LogTypes' => [ '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'import', 'interwiki', 'patrol', 'merge', 'suppress', 'tag', 'managetags', 'contentmodel', 'renameuser', ], 'LogRestrictions' => [ 'suppress' => 'suppressionlog', ], 'FilterLogTypes' => [ 'patrol' => true, 'tag' => true, 'newusers' => false, ], 'LogNames' => [ '' => 'all-logs-page', 'block' => 'blocklogpage', 'protect' => 'protectlogpage', 'rights' => 'rightslog', 'delete' => 'dellogpage', 'upload' => 'uploadlogpage', 'move' => 'movelogpage', 'import' => 'importlogpage', 'patrol' => 'patrol-log-page', 'merge' => 'mergelog', 'suppress' => 'suppressionlog', ], 'LogHeaders' => [ '' => 'alllogstext', 'block' => 'blocklogtext', 'delete' => 'dellogpagetext', 'import' => 'importlogpagetext', 'merge' => 'mergelogpagetext', 'move' => 'movelogpagetext', 'patrol' => 'patrol-log-header', 'protect' => 'protectlogtext', 'rights' => 'rightslogtext', 'suppress' => 'suppressionlogtext', 'upload' => 'uploadlogpagetext', ], 'LogActions' => [ ], 'LogActionsHandlers' => [ 'block/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/unblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'contentmodel/change' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'contentmodel/new' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'delete/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir2' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/restore' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'import/interwiki' => 'MediaWiki\\Logging\\ImportLogFormatter', 'import/upload' => 'MediaWiki\\Logging\\ImportLogFormatter', 'interwiki/iw_add' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_delete' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_edit' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'managetags/activate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/create' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/deactivate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/delete' => 'MediaWiki\\Logging\\LogFormatter', 'merge/merge' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'merge/merge-into' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move_redir' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'patrol/patrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'patrol/autopatrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'protect/modify' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/move_prot' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/protect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/unprotect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'renameuser/renameuser' => [ 'class' => 'MediaWiki\\Logging\\RenameuserLogFormatter', 'services' => [ 'TitleParser', ], ], 'rights/autopromote' => 'MediaWiki\\Logging\\RightsLogFormatter', 'rights/rights' => 'MediaWiki\\Logging\\RightsLogFormatter', 'suppress/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'tag/update' => 'MediaWiki\\Logging\\TagLogFormatter', 'upload/overwrite' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/revert' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/upload' => 'MediaWiki\\Logging\\UploadLogFormatter', ], 'ActionFilteredLogs' => [ 'block' => [ 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], 'unblock' => [ 'unblock', ], ], 'contentmodel' => [ 'change' => [ 'change', ], 'new' => [ 'new', ], ], 'delete' => [ 'delete' => [ 'delete', ], 'delete_redir' => [ 'delete_redir', 'delete_redir2', ], 'restore' => [ 'restore', ], 'event' => [ 'event', ], 'revision' => [ 'revision', ], ], 'import' => [ 'interwiki' => [ 'interwiki', ], 'upload' => [ 'upload', ], ], 'managetags' => [ 'create' => [ 'create', ], 'delete' => [ 'delete', ], 'activate' => [ 'activate', ], 'deactivate' => [ 'deactivate', ], ], 'move' => [ 'move' => [ 'move', ], 'move_redir' => [ 'move_redir', ], ], 'newusers' => [ 'create' => [ 'create', 'newusers', ], 'create2' => [ 'create2', ], 'autocreate' => [ 'autocreate', ], 'byemail' => [ 'byemail', ], ], 'protect' => [ 'protect' => [ 'protect', ], 'modify' => [ 'modify', ], 'unprotect' => [ 'unprotect', ], 'move_prot' => [ 'move_prot', ], ], 'rights' => [ 'rights' => [ 'rights', ], 'autopromote' => [ 'autopromote', ], ], 'suppress' => [ 'event' => [ 'event', ], 'revision' => [ 'revision', ], 'delete' => [ 'delete', ], 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], ], 'upload' => [ 'upload' => [ 'upload', ], 'overwrite' => [ 'overwrite', ], 'revert' => [ 'revert', ], ], ], 'NewUserLog' => true, 'PageCreationLog' => true, 'AllowSpecialInclusion' => true, 'DisableQueryPageUpdate' => false, 'CountCategorizedImagesAsUsed' => false, 'MaxRedirectLinksRetrieved' => 500, 'RangeContributionsCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 32, ], 'Actions' => [ ], 'DefaultRobotPolicy' => 'index,follow', 'NamespaceRobotPolicies' => [ ], 'ArticleRobotPolicies' => [ ], 'ExemptFromUserRobotsControl' => null, 'DebugAPI' => false, 'APIModules' => [ ], 'APIFormatModules' => [ ], 'APIMetaModules' => [ ], 'APIPropModules' => [ ], 'APIListModules' => [ ], 'APIMaxDBRows' => 5000, 'APIMaxResultSize' => 8388608, 'APIMaxUncachedDiffs' => 1, 'APIMaxLagThreshold' => 7, 'APICacheHelpTimeout' => 3600, 'APIUselessQueryPages' => [ 'MIMEsearch', 'LinkSearch', ], 'AjaxLicensePreview' => true, 'CrossSiteAJAXdomains' => [ ], 'CrossSiteAJAXdomainExceptions' => [ ], 'AllowedCorsHeaders' => [ 'Accept', 'Accept-Language', 'Content-Language', 'Content-Type', 'Accept-Encoding', 'DNT', 'Origin', 'User-Agent', 'Api-User-Agent', 'Promise-Non-Write-API-Action', 'Access-Control-Max-Age', 'Authorization', ], 'RestAPIAdditionalRouteFiles' => [ ], 'RestSandboxSpecs' => [ ], 'RestModuleOverrides' => [ ], 'MaxShellMemory' => 307200, 'MaxShellFileSize' => 102400, 'MaxShellTime' => 180, 'MaxShellWallClockTime' => 180, 'ShellCgroup' => false, 'PhpCli' => '/usr/bin/php', 'ShellRestrictionMethod' => 'autodetect', 'ShellboxUrls' => [ 'default' => null, ], 'ShellboxSecretKey' => null, 'ShellboxShell' => '/bin/sh', 'HTTPTimeout' => 25, 'HTTPConnectTimeout' => 5.0, 'HTTPMaxTimeout' => 0, 'HTTPMaxConnectTimeout' => 0, 'HTTPImportTimeout' => 25, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, 'GenerateReqIDFormat' => 'rand24', 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => null, 'VirtualRestConfig' => [ 'paths' => [ ], 'modules' => [ ], 'global' => [ 'timeout' => 360, 'forwardCookies' => false, 'HTTPProxy' => null, ], ], 'EventRelayerConfig' => [ 'default' => [ 'class' => 'Wikimedia\\EventRelayer\\EventRelayerNull', ], ], 'Pingback' => false, 'OriginTrials' => [ ], 'ReportToExpiry' => 86400, 'ReportToEndpoints' => [ ], 'FeaturePolicyReportOnly' => [ ], 'SkinsPreferred' => [ 'vector-2022', 'vector', ], 'SpecialContributeSkinsEnabled' => [ ], 'SpecialContributeNewPageTarget' => null, 'EnableEditRecovery' => false, 'EditRecoveryExpiry' => 2592000, 'UseCodexSpecialBlock' => false, 'ShowLogoutConfirmation' => false, 'EnableProtectionIndicators' => true, 'OutputPipelineStages' => [ ], 'FeatureShutdown' => [ ], 'CloneArticleParserOutput' => true, 'UseLeximorph' => false, 'UsePostprocCacheLegacy' => false, 'UsePostprocCacheParsoid' => true, 'ParserOptionsLogUnsafeSampleRate' => 0, ], 'type' => [ 'ConfigRegistry' => 'object', 'AssumeProxiesUseDefaultProtocolPorts' => 'boolean', 'ForceHTTPS' => 'boolean', 'ExtensionDirectory' => [ 'string', 'null', ], 'StyleDirectory' => [ 'string', 'null', ], 'UploadDirectory' => [ 'string', 'boolean', 'null', ], 'Logos' => [ 'object', 'boolean', ], 'ReferrerPolicy' => [ 'array', 'string', 'boolean', ], 'ActionPaths' => 'object', 'MainPageIsDomainRoot' => 'boolean', 'ImgAuthUrlPathMap' => 'object', 'LocalFileRepo' => 'object', 'ForeignFileRepos' => 'array', 'UseSharedUploads' => 'boolean', 'SharedUploadDirectory' => [ 'string', 'null', ], 'SharedUploadPath' => [ 'string', 'null', ], 'HashedSharedUploadDirectory' => 'boolean', 'FetchCommonsDescriptions' => 'boolean', 'SharedUploadDBname' => [ 'boolean', 'string', ], 'SharedUploadDBprefix' => 'string', 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'CopyUploadsDomains' => 'array', 'CopyUploadTimeout' => [ 'boolean', 'integer', ], 'SharedThumbnailScriptPath' => [ 'string', 'boolean', ], 'HashedUploadDirectory' => 'boolean', 'CSPUploadEntryPoint' => 'boolean', 'FileExtensions' => 'array', 'ProhibitedFileExtensions' => 'array', 'MimeTypeExclusions' => 'array', 'TrustedMediaFormats' => 'array', 'MediaHandlers' => 'object', 'NativeImageLazyLoading' => 'boolean', 'ParserTestMediaHandlers' => 'object', 'MaxInterlacingAreas' => 'object', 'SVGConverters' => 'object', 'SVGNativeRendering' => [ 'string', 'boolean', ], 'MaxImageArea' => [ 'string', 'integer', 'boolean', ], 'TiffThumbnailType' => 'array', 'GenerateThumbnailOnParse' => 'boolean', 'EnableAutoRotation' => [ 'boolean', 'null', ], 'Antivirus' => [ 'string', 'null', ], 'AntivirusSetup' => 'object', 'MimeDetectorCommand' => [ 'string', 'null', ], 'XMLMimeTypes' => 'object', 'ImageLimits' => 'array', 'ThumbLimits' => 'array', 'ThumbnailNamespaces' => 'array', 'ThumbnailSteps' => [ 'array', 'null', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EmailConfirmationBanner' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', 'FileSchemaMigrationStage' => 'integer', 'ExternalLinksDomainGaps' => 'object', 'ContentHandlers' => 'object', 'NamespaceContentModels' => 'object', 'TextModelsToParse' => 'array', 'ExternalStores' => 'array', 'ExternalServers' => 'object', 'DefaultExternalStore' => [ 'array', 'boolean', ], 'RevisionCacheExpiry' => 'integer', 'PageLanguageUseDB' => 'boolean', 'DiffEngine' => [ 'string', 'null', ], 'ExternalDiffEngine' => [ 'string', 'boolean', ], 'Wikidiff2Options' => 'object', 'RequestTimeLimit' => [ 'integer', 'null', ], 'CriticalSectionTimeLimit' => 'number', 'PoolCounterConf' => [ 'object', 'null', ], 'PoolCountClientConf' => 'object', 'MaxUserDBWriteDuration' => [ 'integer', 'boolean', ], 'MaxJobDBWriteDuration' => [ 'integer', 'boolean', ], 'MultiShardSiteStats' => 'boolean', 'ObjectCaches' => 'object', 'WANObjectCache' => 'object', 'MicroStashType' => [ 'string', 'integer', ], 'ParsoidCacheConfig' => 'object', 'ParsoidSelectiveUpdateSampleRate' => 'integer', 'ParserCacheFilterConfig' => 'object', 'ChronologyProtectorSecret' => 'string', 'PHPSessionHandling' => 'string', 'SuspiciousIpExpiry' => [ 'integer', 'boolean', ], 'MemCachedServers' => 'array', 'LocalisationCacheConf' => 'object', 'ExtensionInfoMTime' => [ 'integer', 'boolean', ], 'CdnServers' => 'object', 'CdnServersNoPurge' => 'object', 'HTCPRouting' => 'object', 'GrammarForms' => 'object', 'ExtraInterlanguageLinkPrefixes' => 'array', 'InterlanguageLinkCodeMap' => 'object', 'ExtraLanguageNames' => 'object', 'ExtraLanguageCodes' => 'object', 'DummyLanguageCodes' => 'object', 'DisabledVariants' => 'object', 'ForceUIMsgAsContentMsg' => 'object', 'RawHtmlMessages' => 'array', 'OverrideUcfirstCharacters' => 'object', 'XhtmlNamespaces' => 'object', 'BrowserFormatDetection' => 'string', 'SkinMetaTags' => 'object', 'SkipSkins' => 'object', 'FragmentMode' => 'array', 'FooterIcons' => 'object', 'InterwikiLogoOverride' => 'array', 'ResourceModules' => 'object', 'ResourceModuleSkinStyles' => 'object', 'ResourceLoaderSources' => 'object', 'ResourceLoaderMaxage' => 'object', 'ResourceLoaderMaxQueryLength' => [ 'integer', 'boolean', ], 'CanonicalNamespaceNames' => 'object', 'ExtraNamespaces' => 'object', 'ExtraGenderNamespaces' => 'object', 'NamespaceAliases' => 'object', 'CapitalLinkOverrides' => 'object', 'NamespacesWithSubpages' => 'object', 'NamespacesWithoutAutoSummaries' => 'array', 'ContentNamespaces' => 'array', 'ShortPagesNamespaceExclusions' => 'array', 'ExtraSignatureNamespaces' => 'array', 'InvalidRedirectTargets' => 'array', 'LocalInterwikis' => 'array', 'InterwikiCache' => [ 'boolean', 'object', ], 'SiteTypes' => 'object', 'UrlProtocols' => 'array', 'TidyConfig' => 'object', 'ParsoidSettings' => 'object', 'ParsoidExperimentalParserFunctionOutput' => 'boolean', 'NoFollowNsExceptions' => 'array', 'NoFollowDomainExceptions' => 'array', 'ExternalLinksIgnoreDomains' => 'array', 'EnableMagicLinks' => 'object', 'ManualRevertSearchRadius' => 'integer', 'RevertedTagMaxDepth' => 'integer', 'CentralIdLookupProviders' => 'object', 'CentralIdLookupProvider' => 'string', 'UserRegistrationProviders' => 'object', 'PasswordPolicy' => 'object', 'AuthManagerConfig' => [ 'object', 'null', ], 'AuthManagerAutoConfig' => 'object', 'RememberMe' => 'string', 'ReauthenticateTime' => 'object', 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => 'object', 'ChangeCredentialsBlacklist' => 'array', 'RemoveCredentialsBlacklist' => 'array', 'PasswordConfig' => 'object', 'PasswordResetRoutes' => 'object', 'SignatureAllowedLintErrors' => 'array', 'ReservedUsernames' => 'array', 'DefaultUserOptions' => 'object', 'ConditionalUserOptions' => 'object', 'HiddenPrefs' => 'array', 'UserJsPrefLimit' => 'integer', 'AuthenticationTokenVersion' => [ 'string', 'null', ], 'SessionProviders' => 'object', 'AutoCreateTempUser' => 'object', 'AutoblockExemptions' => 'array', 'BlockCIDRLimit' => 'object', 'EnableMultiBlocks' => 'boolean', 'GroupPermissions' => 'object', 'PrivilegedGroups' => 'array', 'RevokePermissions' => 'object', 'GroupInheritsPermissions' => 'object', 'ImplicitGroups' => 'array', 'GroupsAddToSelf' => 'object', 'GroupsRemoveFromSelf' => 'object', 'RestrictedGroups' => 'object', 'UserRequirementsPrivateConditions' => 'array', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', 'NonincludableNamespaces' => 'object', 'Autopromote' => 'object', 'AutopromoteOnce' => 'object', 'AutopromoteOnceRCExcludedGroups' => 'array', 'AddGroups' => 'object', 'RemoveGroups' => 'object', 'AvailableRights' => 'array', 'ImplicitRights' => 'array', 'AccountCreationThrottle' => [ 'integer', 'array', ], 'TempAccountCreationThrottle' => 'array', 'TempAccountNameAcquisitionThrottle' => 'array', 'SpamRegex' => 'array', 'SummarySpamRegex' => 'array', 'DnsBlacklistUrls' => 'array', 'ProxyList' => [ 'string', 'array', ], 'ProxyWhitelist' => 'array', 'SoftBlockRanges' => 'array', 'RateLimits' => 'object', 'RateLimitsExcludedIPs' => 'array', 'ExternalQuerySources' => 'object', 'PasswordAttemptThrottle' => 'array', 'GrantPermissions' => 'object', 'GrantPermissionGroups' => 'object', 'GrantRiskGroups' => 'object', 'EnableBotPasswords' => 'boolean', 'BotPasswordsCluster' => [ 'string', 'boolean', ], 'BotPasswordsDatabase' => [ 'string', 'boolean', ], 'BotPasswordsLimit' => 'integer', 'CSPHeader' => [ 'boolean', 'object', ], 'CSPReportOnlyHeader' => [ 'boolean', 'object', ], 'CSPUseReportURIDirective' => [ 'boolean', 'object', ], 'CSPFalsePositiveUrls' => 'object', 'AllowCrossOrigin' => 'boolean', 'RestAllowCrossOriginCookieAuth' => 'boolean', 'CookieSameSite' => [ 'string', 'null', ], 'CacheVaryCookies' => 'array', 'TrxProfilerLimits' => 'object', 'DebugLogGroups' => 'object', 'MWLoggerDefaultSpi' => 'object', 'Profiler' => 'object', 'StatsTarget' => [ 'string', 'null', ], 'StatsFormat' => [ 'string', 'null', ], 'StatsPrefix' => 'string', 'OpenTelemetryConfig' => [ 'object', 'null', ], 'OpenSearchTemplates' => 'object', 'NamespacesToBeSearchedDefault' => 'object', 'SitemapNamespaces' => [ 'boolean', 'array', ], 'SitemapNamespacesPriorities' => [ 'boolean', 'object', ], 'SitemapApiConfig' => 'object', 'SpecialSearchFormOptions' => 'object', 'SearchMatchRedirectPreference' => 'boolean', 'SearchRunSuggestedQuery' => 'boolean', 'PreviewOnOpenNamespaces' => 'object', 'ReadOnlyWatchedItemStore' => 'boolean', 'GitRepositoryViewers' => 'object', 'InstallerInitialPages' => 'array', 'RCLinkLimits' => 'array', 'RCLinkDays' => 'array', 'RCFeeds' => 'object', 'OverrideSiteFeed' => 'object', 'FeedClasses' => 'object', 'AdvertisedFeedTypes' => 'array', 'SoftwareTags' => 'object', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => 'boolean', 'EnableWatchstarPopover' => 'boolean', 'EnableWatchlistLabels' => 'boolean', 'WatchlistLabelsMaxPerUser' => 'integer', 'WatchlistPurgeRate' => 'number', 'WatchlistExpiryMaxDuration' => [ 'string', 'null', ], 'EnableChangesListQueryPartitioning' => 'boolean', 'ImportSources' => 'object', 'ExtensionFunctions' => 'array', 'ExtensionMessagesFiles' => 'object', 'MessagesDirs' => 'object', 'TranslationAliasesDirs' => 'object', 'ExtensionEntryPointListFiles' => 'object', 'ValidSkinNames' => 'object', 'SpecialPages' => 'object', 'ExtensionCredits' => 'object', 'Hooks' => 'object', 'ServiceWiringFiles' => 'array', 'JobClasses' => 'object', 'JobTypesExcludedFromDefaultQueue' => 'array', 'JobBackoffThrottling' => 'object', 'JobTypeConf' => 'object', 'SpecialPageCacheUpdates' => 'object', 'PagePropLinkInvalidations' => 'object', 'TempCategoryCollations' => 'array', 'SortedCategories' => 'boolean', 'TrackingCategories' => 'array', 'LogTypes' => 'array', 'LogRestrictions' => 'object', 'FilterLogTypes' => 'object', 'LogNames' => 'object', 'LogHeaders' => 'object', 'LogActions' => 'object', 'LogActionsHandlers' => 'object', 'ActionFilteredLogs' => 'object', 'RangeContributionsCIDRLimit' => 'object', 'Actions' => 'object', 'NamespaceRobotPolicies' => 'object', 'ArticleRobotPolicies' => 'object', 'ExemptFromUserRobotsControl' => [ 'array', 'null', ], 'APIModules' => 'object', 'APIFormatModules' => 'object', 'APIMetaModules' => 'object', 'APIPropModules' => 'object', 'APIListModules' => 'object', 'APIUselessQueryPages' => 'array', 'CrossSiteAJAXdomains' => 'object', 'CrossSiteAJAXdomainExceptions' => 'object', 'AllowedCorsHeaders' => 'array', 'RestAPIAdditionalRouteFiles' => 'array', 'RestSandboxSpecs' => 'object', 'RestModuleOverrides' => 'object', 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'GenerateReqIDFormat' => 'string', 'VirtualRestConfig' => 'object', 'EventRelayerConfig' => 'object', 'Pingback' => 'boolean', 'OriginTrials' => 'array', 'ReportToExpiry' => 'integer', 'ReportToEndpoints' => 'array', 'FeaturePolicyReportOnly' => 'array', 'SkinsPreferred' => 'array', 'SpecialContributeSkinsEnabled' => 'array', 'SpecialContributeNewPageTarget' => [ 'string', 'null', ], 'EnableEditRecovery' => 'boolean', 'EditRecoveryExpiry' => 'integer', 'UseCodexSpecialBlock' => 'boolean', 'ShowLogoutConfirmation' => 'boolean', 'EnableProtectionIndicators' => 'boolean', 'OutputPipelineStages' => 'object', 'FeatureShutdown' => 'array', 'CloneArticleParserOutput' => 'boolean', 'UseLeximorph' => 'boolean', 'UsePostprocCacheLegacy' => 'boolean', 'UsePostprocCacheParsoid' => 'boolean', 'ParserOptionsLogUnsafeSampleRate' => 'integer', ], 'mergeStrategy' => [ 'TiffThumbnailType' => 'replace', 'LBFactoryConf' => 'replace', 'InterwikiCache' => 'replace', 'PasswordPolicy' => 'array_replace_recursive', 'AuthManagerAutoConfig' => 'array_plus_2d', 'GroupPermissions' => 'array_plus_2d', 'RevokePermissions' => 'array_plus_2d', 'AddGroups' => 'array_merge_recursive', 'RemoveGroups' => 'array_merge_recursive', 'RateLimits' => 'array_plus_2d', 'GrantPermissions' => 'array_plus_2d', 'MWLoggerDefaultSpi' => 'replace', 'Profiler' => 'replace', 'Hooks' => 'array_merge_recursive', 'RestModuleOverrides' => 'array_replace_recursive', 'VirtualRestConfig' => 'array_plus_2d', ], 'dynamicDefault' => [ 'UsePathInfo' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUsePathInfo', ], ], 'Script' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultScript', ], ], 'LoadScript' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLoadScript', ], ], 'RestPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultRestPath', ], ], 'StylePath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultStylePath', ], ], 'LocalStylePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalStylePath', ], ], 'ExtensionAssetsPath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultExtensionAssetsPath', ], ], 'ArticlePath' => [ 'use' => [ 'Script', 'UsePathInfo', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultArticlePath', ], ], 'UploadPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUploadPath', ], ], 'FileCacheDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultFileCacheDirectory', ], ], 'Logo' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLogo', ], ], 'DeletedDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDeletedDirectory', ], ], 'ShowEXIF' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultShowEXIF', ], ], 'SharedPrefix' => [ 'use' => [ 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedPrefix', ], ], 'SharedSchema' => [ 'use' => [ 'DBmwschema', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedSchema', ], ], 'DBerrorLogTZ' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDBerrorLogTZ', ], ], 'Localtimezone' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocaltimezone', ], ], 'LocalTZoffset' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalTZoffset', ], ], 'ResourceBasePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultResourceBasePath', ], ], 'MetaNamespace' => [ 'use' => [ 'Sitename', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultMetaNamespace', ], ], 'CookieSecure' => [ 'use' => [ 'ForceHTTPS', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookieSecure', ], ], 'CookiePrefix' => [ 'use' => [ 'SharedDB', 'SharedPrefix', 'SharedTables', 'DBname', 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookiePrefix', ], ], 'ReadOnlyFile' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultReadOnlyFile', ], ], ], ], 'config-schema' => [ 'UploadStashScalerBaseUrl' => [ 'deprecated' => 'since 1.36 Use thumbProxyUrl in $wgLocalFileRepo', ], 'IllegalFileChars' => [ 'deprecated' => 'since 1.41; no longer customizable', ], 'ThumbnailNamespaces' => [ 'items' => [ 'type' => 'integer', ], ], 'LocalDatabases' => [ 'items' => [ 'type' => 'string', ], ], 'ParserCacheFilterConfig' => [ 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of namespace IDs to filter definitions.', 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of filter names to values.', 'properties' => [ 'minCpuTime' => [ 'type' => 'number', ], ], ], ], ], 'PHPSessionHandling' => [ 'deprecated' => 'since 1.45 Integration with PHP session handling will be removed in the future', ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'ChangeCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'RemoveCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'GroupPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GroupInheritsPermissions' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'AvailableRights' => [ 'items' => [ 'type' => 'string', ], ], 'ImplicitRights' => [ 'items' => [ 'type' => 'string', ], ], 'SoftBlockRanges' => [ 'items' => [ 'type' => 'string', ], ], 'ExternalQuerySources' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'enabled' => [ 'type' => 'boolean', 'default' => false, ], 'url' => [ 'type' => 'string', 'format' => 'uri', ], 'timeout' => [ 'type' => 'integer', 'default' => 10, ], ], 'required' => [ 'enabled', 'url', ], 'additionalProperties' => false, ], ], 'GrantPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GrantPermissionGroups' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'SitemapNamespacesPriorities' => [ 'deprecated' => 'since 1.45 and ignored', ], 'SitemapApiConfig' => [ 'additionalProperties' => [ 'enabled' => [ 'type' => 'bool', ], 'sitemapsPerIndex' => [ 'type' => 'int', ], 'pagesPerSitemap' => [ 'type' => 'int', ], 'expiry' => [ 'type' => 'int', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'JobBackoffThrottling' => [ 'additionalProperties' => [ 'type' => 'number', ], ], 'JobTypeConf' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'class' => [ 'type' => 'string', ], 'order' => [ 'type' => 'string', ], 'claimTTL' => [ 'type' => 'integer', ], ], ], ], 'TrackingCategories' => [ 'deprecated' => 'since 1.25 Extensions should now register tracking categories using the new extension registration system.', ], 'RangeContributionsCIDRLimit' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'RestSandboxSpecs' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'url' => [ 'type' => 'string', 'format' => 'url', ], 'name' => [ 'type' => 'string', ], 'file' => [ 'type' => 'string', ], 'msg' => [ 'type' => 'string', 'description' => 'a message key', ], ], ], ], 'RestModuleOverrides' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'mode' => [ 'type' => 'string', ], ], 'required' => [ 'mode', ], ], ], 'ShellboxUrls' => [ 'additionalProperties' => [ 'type' => [ 'string', 'boolean', 'null', ], ], ], ], 'obsolete-config' => [ 'MangleFlashPolicy' => 'Since 1.39; no longer has any effect.', 'EnableOpenSearchSuggest' => 'Since 1.35, no longer used', 'AutoloadAttemptLowercase' => 'Since 1.40; no longer has any effect.', ],]
Generic interface providing Time-To-Live constants for expirable object storage.
Key-encoding methods for object caching (BagOStuff and WANObjectCache)
Represents an OpenTelemetry span, i.e.
Base interface for an OpenTelemetry tracer responsible for creating spans.