aboutsummaryrefslogtreecommitdiffstats
path: root/includes/objectcache
diff options
context:
space:
mode:
Diffstat (limited to 'includes/objectcache')
-rw-r--r--includes/objectcache/ObjectCache.php209
-rw-r--r--includes/objectcache/ObjectCacheFactory.php295
2 files changed, 331 insertions, 173 deletions
diff --git a/includes/objectcache/ObjectCache.php b/includes/objectcache/ObjectCache.php
index 91ea0a57daee..129bb46d1268 100644
--- a/includes/objectcache/ObjectCache.php
+++ b/includes/objectcache/ObjectCache.php
@@ -21,12 +21,7 @@
* @ingroup Cache
*/
-use MediaWiki\Deferred\DeferredUpdates;
-use MediaWiki\Http\Telemetry;
-use MediaWiki\Logger\LoggerFactory;
-use MediaWiki\MainConfigNames;
use MediaWiki\MediaWikiServices;
-use MediaWiki\WikiMap\WikiMap;
/**
* Functions to get cache objects
@@ -66,7 +61,10 @@ use MediaWiki\WikiMap\WikiMap;
* @ingroup Cache
*/
class ObjectCache {
- /** @var BagOStuff[] Map of (id => BagOStuff) */
+ /**
+ * @deprecated Use ObjectCacheFactory instead.
+ * @var BagOStuff[] Map of (id => BagOStuff)
+ */
public static $instances = [];
/**
@@ -78,157 +76,26 @@ class ObjectCache {
/**
* Get a cached instance of the specified type of cache object.
*
- * @param string|int $id A key in $wgObjectCaches.
- * @return BagOStuff
- */
- public static function getInstance( $id ) {
- if ( !isset( self::$instances[$id] ) ) {
- self::$instances[$id] = self::newFromId( $id );
- }
-
- return self::$instances[$id];
- }
-
- /**
- * Create a new cache object of the specified type.
+ * @deprecated Use ObjectCacheFactory::getInstance instead.
*
* @param string|int $id A key in $wgObjectCaches.
* @return BagOStuff
- * @throws InvalidArgumentException
*/
- private static function newFromId( $id ) {
- global $wgObjectCaches;
-
- if ( !isset( $wgObjectCaches[$id] ) ) {
- // Always recognize these ones
- if ( $id === CACHE_NONE ) {
- return new EmptyBagOStuff();
- } elseif ( $id === CACHE_HASH ) {
- return new HashBagOStuff();
- }
-
- throw new InvalidArgumentException( "Invalid object cache type \"$id\" requested. " .
- "It is not present in \$wgObjectCaches." );
- }
-
- return self::newFromParams( $wgObjectCaches[$id] );
+ public static function getInstance( $id ) {
+ return MediaWikiServices::getInstance()->getObjectCacheFactory()->getInstance( $id );
}
/**
- * Get the default keyspace for this wiki.
+ * @see ObjectCacheFactory::newFromParams()
*
- * This is either the value of the `CachePrefix` configuration variable,
- * or (if the former is unset) the `DBname` configuration variable, with
- * `DBprefix` (if defined).
- *
- * @return string
- */
- private static function getDefaultKeyspace() {
- global $wgCachePrefix;
-
- $keyspace = $wgCachePrefix;
- if ( is_string( $keyspace ) && $keyspace !== '' ) {
- return $keyspace;
- }
-
- return WikiMap::getCurrentWikiDbDomain()->getId();
- }
-
- /**
- * Create a new cache object from parameters.
+ * @deprecated since 1.42, Use ObjectCacheFactory::newFromParams instead.
+ * @param array $params
*
- * @param array $params Must have 'factory' or 'class' property.
- * - factory: Callback passed $params that returns BagOStuff.
- * - class: BagOStuff subclass constructed with $params.
- * - loggroup: Alias to set 'logger' key with LoggerFactory group.
- * - .. Other parameters passed to factory or class.
- * @param MediaWikiServices|null $services [internal]
* @return BagOStuff
*/
- public static function newFromParams( array $params, MediaWikiServices $services = null ) {
- $services ??= MediaWikiServices::getInstance();
- $conf = $services->getMainConfig();
-
- // Apply default parameters and resolve the logger instance
- $params += [
- 'logger' => LoggerFactory::getInstance( $params['loggroup'] ?? 'objectcache' ),
- 'keyspace' => self::getDefaultKeyspace(),
- 'asyncHandler' => [ DeferredUpdates::class, 'addCallableUpdate' ],
- 'reportDupes' => true,
- 'stats' => $services->getStatsFactory(),
- ];
-
- if ( isset( $params['factory'] ) ) {
- $args = $params['args'] ?? [ $params ];
-
- return call_user_func( $params['factory'], ...$args );
- }
-
- if ( !isset( $params['class'] ) ) {
- throw new InvalidArgumentException(
- 'No "factory" nor "class" provided; got "' . print_r( $params, true ) . '"'
- );
- }
-
- $class = $params['class'];
-
- // Normalization and DI for SqlBagOStuff
- if ( is_a( $class, SqlBagOStuff::class, true ) ) {
- if ( isset( $params['globalKeyLB'] ) ) {
- throw new InvalidArgumentException(
- 'globalKeyLB in $wgObjectCaches is no longer supported' );
- }
- if ( isset( $params['server'] ) && !isset( $params['servers'] ) ) {
- $params['servers'] = [ $params['server'] ];
- unset( $params['server'] );
- }
- if ( isset( $params['servers'] ) ) {
- // In the past it was not required to set 'dbDirectory' in $wgObjectCaches
- foreach ( $params['servers'] as &$server ) {
- if ( $server['type'] === 'sqlite' && !isset( $server['dbDirectory'] ) ) {
- $server['dbDirectory'] = $conf->get( MainConfigNames::SQLiteDataDir );
- }
- }
- } elseif ( isset( $params['cluster'] ) ) {
- $cluster = $params['cluster'];
- $params['loadBalancerCallback'] = static function () use ( $services, $cluster ) {
- return $services->getDBLoadBalancerFactory()->getExternalLB( $cluster );
- };
- $params += [ 'dbDomain' => false ];
- } else {
- $params['loadBalancerCallback'] = static function () use ( $services ) {
- return $services->getDBLoadBalancer();
- };
- $params += [ 'dbDomain' => false ];
- }
- $params += [ 'writeBatchSize' => $conf->get( MainConfigNames::UpdateRowsPerQuery ) ];
- }
-
- // Normalization and DI for MemcachedBagOStuff
- if ( is_subclass_of( $class, MemcachedBagOStuff::class ) ) {
- $params += [
- 'servers' => $conf->get( MainConfigNames::MemCachedServers ),
- 'persistent' => $conf->get( MainConfigNames::MemCachedPersistent ),
- 'timeout' => $conf->get( MainConfigNames::MemCachedTimeout ),
- ];
- }
-
- // Normalization and DI for MultiWriteBagOStuff
- if ( is_a( $class, MultiWriteBagOStuff::class, true ) ) {
- // Phan warns about foreach with non-array because it
- // thinks any key can be Closure|IBufferingStatsdDataFactory
- '@phan-var array{caches:array[]} $params';
- foreach ( $params['caches'] ?? [] as $i => $cacheInfo ) {
- // Ensure logger, keyspace, asyncHandler, etc are injected just as if
- // one of these was configured without MultiWriteBagOStuff.
- $params['caches'][$i] = self::newFromParams( $cacheInfo, $services );
- }
- }
- if ( is_a( $class, RESTBagOStuff::class, true ) ) {
- $params['telemetry'] = Telemetry::getInstance();
- }
-
- return new $class( $params );
+ public static function newFromParams( array $params ) {
+ return MediaWikiServices::getInstance()->getObjectCacheFactory()
+ ->newFromParams( $params );
}
/**
@@ -241,18 +108,23 @@ class ObjectCache {
* If no cache choice is configured (by default $wgMainCacheType is CACHE_NONE),
* then CACHE_ANYTHING will forward to CACHE_DB.
*
- * @param array $params
+ * @deprecated since 1.42,
+ * Use ObjectCacheFactory::newInstance( ObjectCache::getAnythingId() );
+ *
* @return BagOStuff
*/
- public static function newAnything( $params ) {
- return self::getInstance( self::getAnythingId() );
+ public static function newAnything() {
+ return MediaWikiServices::getInstance()->getObjectCacheFactory()
+ ->getInstance( self::getAnythingId() );
}
/**
+ * @internal Used by ObjectCacheFactory and ObjectCache.
+ *
* Get the ID that will be used for CACHE_ANYTHING
* @return string|int
*/
- private static function getAnythingId() {
+ public static function getAnythingId() {
global $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType;
$candidates = [ $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType ];
foreach ( $candidates as $candidate ) {
@@ -284,16 +156,8 @@ class ObjectCache {
}
/**
- * Factory function for CACHE_ACCEL (referenced from configuration)
- *
- * This will look for any APC or APCu style server-local cache.
- * A fallback cache can be specified if none is found.
- *
- * // Direct calls
- * ObjectCache::getLocalServerInstance( $fallbackType );
- *
- * // From $wgObjectCaches via newFromParams()
- * ObjectCache::getLocalServerInstance( [ 'fallback' => $fallbackType ] );
+ * @deprecated since 1.42, Use ObjectCacheFactory::getLocaServerInstance()
+ * @see ObjectCacheFactory::getLocalServerInstance()
*
* @param int|string|array $fallback Fallback cache or parameter map with 'fallback'
* @return BagOStuff
@@ -301,15 +165,8 @@ class ObjectCache {
* @since 1.27
*/
public static function getLocalServerInstance( $fallback = CACHE_NONE ) {
- $cache = MediaWikiServices::getInstance()->getLocalServerObjectCache();
- if ( $cache instanceof EmptyBagOStuff ) {
- if ( is_array( $fallback ) ) {
- $fallback = $fallback['fallback'] ?? CACHE_NONE;
- }
- $cache = self::getInstance( $fallback );
- }
-
- return $cache;
+ return MediaWikiServices::getInstance()->getObjectCacheFactory()
+ ->getLocalServerInstance( $fallback );
}
/**
@@ -337,7 +194,7 @@ class ObjectCache {
if ( ( $cache['class'] ?? '' ) === SqlBagOStuff::class ) {
return true;
}
- if ( ( $cache['factory'] ?? '' ) === 'ObjectCache::newAnything' ) {
+ if ( $id === CACHE_ANYTHING ) {
$id = self::getAnythingId();
return self::isDatabaseId( $id );
}
@@ -345,10 +202,12 @@ class ObjectCache {
}
/**
+ * @deprecated since 1.42, Use ObjectCacheFactory::clear() instead.
+ *
* Clear all the cached instances.
*/
public static function clear() {
- self::$instances = [];
+ MediaWikiServices::getInstance()->getObjectCacheFactory()->clear();
}
/**
@@ -362,14 +221,18 @@ class ObjectCache {
* and thus must remain fairly standalone so as to not cause initialization
* of the MediaWikiServices singleton.
*
+ * @internal For use by ServiceWiring and ExtensionRegistry. There are use
+ * cases whereby we want to build up local server cache without service
+ * wiring available.
* @since 1.35
+ * @param string|false $cachePrefix
* @return BagOStuff
*/
- public static function makeLocalServerCache(): BagOStuff {
+ public static function makeLocalServerCache( $cachePrefix ): BagOStuff {
$params = [
'reportDupes' => false,
// Even simple caches must use a keyspace (T247562)
- 'keyspace' => self::getDefaultKeyspace(),
+ 'keyspace' => $cachePrefix,
];
$class = self::getLocalServerCacheClass();
return new $class( $params );
diff --git a/includes/objectcache/ObjectCacheFactory.php b/includes/objectcache/ObjectCacheFactory.php
new file mode 100644
index 000000000000..ebcbda18003e
--- /dev/null
+++ b/includes/objectcache/ObjectCacheFactory.php
@@ -0,0 +1,295 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+use MediaWiki\Config\ServiceOptions;
+use MediaWiki\Http\Telemetry;
+use MediaWiki\Logger\Spi;
+use MediaWiki\MainConfigNames;
+use MediaWiki\MediaWikiServices;
+use MediaWiki\WikiMap\WikiMap;
+use Wikimedia\Stats\StatsFactory;
+
+/**
+ * Factory for cache objects as configured in the ObjectCaches setting.
+ *
+ * @ingroup Cache
+ * @since 1.42
+ */
+class ObjectCacheFactory {
+ /**
+ * @internal For use by ServiceWiring.php
+ * @var array
+ */
+ public const CONSTRUCTOR_OPTIONS = [
+ MainConfigNames::SQLiteDataDir,
+ MainConfigNames::UpdateRowsPerQuery,
+ MainConfigNames::MemCachedServers,
+ MainConfigNames::MemCachedPersistent,
+ MainConfigNames::MemCachedTimeout,
+ MainConfigNames::CachePrefix,
+ MainConfigNames::ObjectCaches,
+ MainConfigNames::MainCacheType,
+ ];
+
+ private ServiceOptions $options;
+ private StatsFactory $stats;
+ private Spi $logger;
+ /** @var BagOStuff[] */
+ private $instances = [];
+
+ public function __construct(
+ ServiceOptions $options,
+ StatsFactory $stats,
+ Spi $loggerSpi
+ ) {
+ $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
+ $this->options = $options;
+ $this->stats = $stats;
+ $this->logger = $loggerSpi;
+ }
+
+ /**
+ * Get the default keyspace for this wiki.
+ *
+ * This is either the value of the `CachePrefix` configuration variable,
+ * or (if the former is unset) the `DBname` configuration variable, with
+ * `DBprefix` (if defined).
+ *
+ * @return string
+ */
+ private function getDefaultKeyspace(): string {
+ $cachePrefix = $this->options->get( MainConfigNames::CachePrefix );
+ if ( is_string( $cachePrefix ) && $cachePrefix !== '' ) {
+ return $cachePrefix;
+ }
+
+ return WikiMap::getCurrentWikiDbDomain()->getId();
+ }
+
+ /**
+ * Create a new cache object of the specified type.
+ *
+ * @param string|int $id A key in $wgObjectCaches.
+ * @return BagOStuff
+ * @throws InvalidArgumentException
+ */
+ private function newFromId( $id ): BagOStuff {
+ if ( $id === CACHE_ANYTHING ) {
+ $id = ObjectCache::getAnythingId();
+ }
+
+ if ( !isset( $this->options->get( MainConfigNames::ObjectCaches )[$id] ) ) {
+ // Always recognize these ones
+ if ( $id === CACHE_NONE ) {
+ return new EmptyBagOStuff();
+ } elseif ( $id === CACHE_HASH ) {
+ return new HashBagOStuff();
+ } elseif ( $id === CACHE_ACCEL ) {
+ return ObjectCache::makeLocalServerCache(
+ $this->options->get( MainConfigNames::CachePrefix )
+ );
+ }
+
+ throw new InvalidArgumentException( "Invalid object cache type \"$id\" requested. " .
+ "It is not present in \$wgObjectCaches." );
+ }
+
+ return $this->newFromParams( $this->options->get( MainConfigNames::ObjectCaches )[$id] );
+ }
+
+ /**
+ * Get a cached instance of the specified type of cache object.
+ *
+ * @param string|int $id A key in $wgObjectCaches.
+ * @return BagOStuff
+ */
+ public function getInstance( $id ): BagOStuff {
+ if ( !isset( $this->instances[$id] ) ) {
+ $this->instances[$id] = $this->newFromId( $id );
+ }
+
+ return $this->instances[$id];
+ }
+
+ /**
+ * @internal Using this method directly outside of MediaWiki core
+ * is discouraged. Use getInstance() instead and supply the ID
+ * of the cache instance to be looked up.
+ *
+ * Create a new cache object from parameters specification supplied.
+ *
+ * @param array $params Must have 'factory' or 'class' property.
+ * - factory: Callback passed $params that returns BagOStuff.
+ * - class: BagOStuff subclass constructed with $params.
+ * - loggroup: Alias to set 'logger' key with LoggerFactory group.
+ * - .. Other parameters passed to factory or class.
+ *
+ * @return BagOStuff
+ */
+ public function newFromParams( array $params ): BagOStuff {
+ $logger = $this->logger->getLogger( $params['loggroup'] ?? 'objectcache' );
+ // Apply default parameters and resolve the logger instance
+ $params += [
+ 'logger' => $logger,
+ 'keyspace' => $this->getDefaultKeyspace(),
+ 'asyncHandler' => [ DeferredUpdates::class, 'addCallableUpdate' ],
+ 'reportDupes' => true,
+ 'stats' => $this->stats,
+ ];
+
+ if ( isset( $params['factory'] ) ) {
+ $args = $params['args'] ?? [ $params ];
+
+ return call_user_func( $params['factory'], ...$args );
+ }
+
+ if ( !isset( $params['class'] ) ) {
+ throw new InvalidArgumentException(
+ 'No "factory" nor "class" provided; got "' . print_r( $params, true ) . '"'
+ );
+ }
+
+ $class = $params['class'];
+
+ // Normalization and DI for SqlBagOStuff
+ if ( is_a( $class, SqlBagOStuff::class, true ) ) {
+ $this->prepareSqlBagOStuffFromParams( $params );
+ }
+
+ // Normalization and DI for MemcachedBagOStuff
+ if ( is_subclass_of( $class, MemcachedBagOStuff::class ) ) {
+ $this->prepareMemcachedBagOStuffFromParams( $params );
+ }
+
+ // Normalization and DI for MultiWriteBagOStuff
+ if ( is_a( $class, MultiWriteBagOStuff::class, true ) ) {
+ $this->prepareMultiWriteBagOStuffFromParams( $params );
+ }
+ if ( is_a( $class, RESTBagOStuff::class, true ) ) {
+ $this->prepareRESTBagOStuffFromParams( $params );
+ }
+
+ return new $class( $params );
+ }
+
+ private function prepareSqlBagOStuffFromParams( array &$params ): void {
+ if ( isset( $params['globalKeyLB'] ) ) {
+ throw new InvalidArgumentException(
+ 'globalKeyLB in $wgObjectCaches is no longer supported' );
+ }
+ if ( isset( $params['server'] ) && !isset( $params['servers'] ) ) {
+ $params['servers'] = [ $params['server'] ];
+ unset( $params['server'] );
+ }
+ if ( isset( $params['servers'] ) ) {
+ // In the past it was not required to set 'dbDirectory' in $wgObjectCaches
+ foreach ( $params['servers'] as &$server ) {
+ if ( $server['type'] === 'sqlite' && !isset( $server['dbDirectory'] ) ) {
+ $server['dbDirectory'] = $this->options->get( MainConfigNames::SQLiteDataDir );
+ }
+ }
+ } elseif ( isset( $params['cluster'] ) ) {
+ $cluster = $params['cluster'];
+ $params['loadBalancerCallback'] = static function () use ( $cluster ) {
+ return MediaWikiServices::getInstance()->getDBLoadBalancerFactory()
+ ->getExternalLB( $cluster );
+ };
+ $params += [ 'dbDomain' => false ];
+ } else {
+ $params['loadBalancerCallback'] = static function () {
+ return MediaWikiServices::getInstance()->getDBLoadBalancer();
+ };
+ $params += [ 'dbDomain' => false ];
+ }
+ $params += [ 'writeBatchSize' => $this->options->get( MainConfigNames::UpdateRowsPerQuery ) ];
+ }
+
+ private function prepareMemcachedBagOStuffFromParams( array &$params ): void {
+ $params += [
+ 'servers' => $this->options->get( MainConfigNames::MemCachedServers ),
+ 'persistent' => $this->options->get( MainConfigNames::MemCachedPersistent ),
+ 'timeout' => $this->options->get( MainConfigNames::MemCachedTimeout ),
+ ];
+ }
+
+ private function prepareMultiWriteBagOStuffFromParams( array &$params ): void {
+ // Phan warns about foreach with non-array because it
+ // thinks any key can be Closure|IBufferingStatsdDataFactory
+ '@phan-var array{caches:array[]} $params';
+ foreach ( $params['caches'] ?? [] as $i => $cacheInfo ) {
+ // Ensure logger, keyspace, asyncHandler, etc are injected just as if
+ // one of these was configured without MultiWriteBagOStuff.
+ $params['caches'][$i] = $this->newFromParams( $cacheInfo );
+ }
+ }
+
+ private function prepareRESTBagOStuffFromParams( array &$params ): void {
+ $params['telemetry'] = Telemetry::getInstance();
+ }
+
+ /**
+ * Factory function for CACHE_ACCEL (referenced from configuration)
+ *
+ * This will look for any APC or APCu style server-local cache.
+ * A fallback cache can be specified if none is found.
+ *
+ * // Direct calls
+ * ObjectCache::getLocalServerInstance( $fallbackType );
+ *
+ * // From $wgObjectCaches via newFromParams()
+ * ObjectCache::getLocalServerInstance( [ 'fallback' => $fallbackType ] );
+ *
+ * @param int|string|array $fallback Fallback cache or parameter map with 'fallback'
+ * @return BagOStuff
+ * @throws InvalidArgumentException
+ */
+ public function getLocalServerInstance( $fallback = CACHE_NONE ): BagOStuff {
+ $cache = $this->getInstance( CACHE_ACCEL );
+ if ( $cache instanceof EmptyBagOStuff ) {
+ if ( is_array( $fallback ) ) {
+ $fallback = $fallback['fallback'] ?? CACHE_NONE;
+ }
+ $cache = $this->getInstance( $fallback );
+ }
+
+ return $cache;
+ }
+
+ /**
+ * Clear all the cached instances.
+ */
+ public function clear(): void {
+ $this->instances = [];
+ }
+
+ /**
+ * @internal For tests ONLY.
+ *
+ * @param string|int $cacheId
+ * @param BagOStuff $cache
+ * @return void
+ */
+ public function setInstanceForTesting( $cacheId, BagOStuff $cache ): void {
+ if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
+ throw new LogicException( __METHOD__ . ' can not be called outside of tests' );
+ }
+ $this->instances[$cacheId] = $cache;
+ }
+}