MediaWiki master
ObjectCacheFactory.php
Go to the documentation of this file.
1<?php
8
9use InvalidArgumentException;
23
66 public const CONSTRUCTOR_OPTIONS = [
77 ];
78
79 private ServiceOptions $options;
80 private StatsFactory $stats;
81 private Spi $logger;
82 private TracerInterface $telemetry;
84 private $instances = [];
85 private string $domainId;
87 private $dbLoadBalancerFactory;
93
94 public function __construct(
95 ServiceOptions $options,
96 StatsFactory $stats,
97 Spi $loggerSpi,
98 callable $dbLoadBalancerFactory,
99 string $domainId,
100 TracerInterface $telemetry
101 ) {
102 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
103 $this->options = $options;
104 $this->stats = $stats;
105 $this->logger = $loggerSpi;
106 $this->dbLoadBalancerFactory = $dbLoadBalancerFactory;
107 $this->domainId = $domainId;
108 $this->telemetry = $telemetry;
109 }
110
118 private function getDefaultKeyspace(): string {
119 $cachePrefix = $this->options->get( MainConfigNames::CachePrefix );
120 if ( is_string( $cachePrefix ) && $cachePrefix !== '' ) {
121 return $cachePrefix;
122 }
123
124 return $this->domainId;
125 }
126
133 private function newFromId( $id ): BagOStuff {
134 if ( $id === CACHE_ANYTHING ) {
135 $id = $this->getAnythingId();
136 }
137
138 if ( !isset( $this->options->get( MainConfigNames::ObjectCaches )[$id] ) ) {
139 // Always recognize these
140 if ( $id === CACHE_NONE ) {
141 return new EmptyBagOStuff();
142 } elseif ( $id === CACHE_HASH ) {
143 return new HashBagOStuff();
144 } elseif ( $id === CACHE_ACCEL ) {
145 return self::makeLocalServerCache( $this->getDefaultKeyspace() );
146 } elseif ( $id === 'wincache' ) {
147 wfDeprecated( __METHOD__ . ' with cache ID "wincache"', '1.43' );
148 return self::makeLocalServerCache( $this->getDefaultKeyspace() );
149 }
150
151 throw new InvalidArgumentException( "Invalid object cache type \"$id\" requested. " .
152 "It is not present in \$wgObjectCaches." );
153 }
154
155 return $this->newFromParams( $this->options->get( MainConfigNames::ObjectCaches )[$id] );
156 }
157
164 public function getInstance( $id ): BagOStuff {
165 if ( !isset( $this->instances[$id] ) ) {
166 $this->instances[$id] = $this->newFromId( $id );
167 }
168
169 return $this->instances[$id];
170 }
171
189 public function newFromParams( array $params ): BagOStuff {
190 $logger = $this->logger->getLogger( $params['loggroup'] ?? 'objectcache' );
191 // Apply default parameters and resolve the logger instance
192 $params += [
193 'logger' => $logger,
194 'keyspace' => $this->getDefaultKeyspace(),
195 // T415142: Must be serializable and cannot use the ( ... ) syntax!
196 'asyncHandler' => [ DeferredUpdates::class, 'addCallableUpdate' ],
197 'reportDupes' => true,
198 'stats' => $this->stats,
199 'telemetry' => $this->telemetry,
200 ];
201
202 if ( isset( $params['factory'] ) ) {
203 $args = $params['args'] ?? [ $params ];
204
205 return $params['factory']( ...$args );
206 }
207
208 if ( !isset( $params['class'] ) ) {
209 throw new InvalidArgumentException(
210 'No "factory" nor "class" provided; got "' . print_r( $params, true ) . '"'
211 );
212 }
213
214 $class = $params['class'];
215
216 // Normalization and DI for SqlBagOStuff
217 if ( is_a( $class, SqlBagOStuff::class, true ) ) {
218 $this->prepareSqlBagOStuffFromParams( $params );
219 }
220
221 // Normalization and DI for MemcachedBagOStuff
222 if ( is_subclass_of( $class, MemcachedBagOStuff::class ) ) {
223 $this->prepareMemcachedBagOStuffFromParams( $params );
224 }
225
226 // Normalization and DI for MultiWriteBagOStuff
227 if ( is_a( $class, MultiWriteBagOStuff::class, true ) ) {
228 $this->prepareMultiWriteBagOStuffFromParams( $params );
229 }
230
231 return new $class( $params );
232 }
233
234 private function prepareSqlBagOStuffFromParams( array &$params ): void {
235 if ( isset( $params['globalKeyLB'] ) ) {
236 throw new InvalidArgumentException(
237 'globalKeyLB in $wgObjectCaches is no longer supported' );
238 }
239 if ( isset( $params['server'] ) && !isset( $params['servers'] ) ) {
240 $params['servers'] = [ $params['server'] ];
241 unset( $params['server'] );
242 }
243 if ( isset( $params['servers'] ) ) {
244 // In the past it was not required to set 'dbDirectory' in $wgObjectCaches
245 foreach ( $params['servers'] as &$server ) {
246 if ( $server['type'] === 'sqlite' && !isset( $server['dbDirectory'] ) ) {
247 $server['dbDirectory'] = $this->options->get( MainConfigNames::SQLiteDataDir );
248 }
249 }
250 } elseif ( isset( $params['cluster'] ) ) {
251 $cluster = $params['cluster'];
252 $dbLbFactory = $this->dbLoadBalancerFactory;
253 $params['loadBalancerCallback'] = static function () use ( $cluster, $dbLbFactory ) {
254 return $dbLbFactory()->getExternalLB( $cluster );
255 };
256 $params += [ 'dbDomain' => false ];
257 } else {
258 $dbLbFactory = $this->dbLoadBalancerFactory;
259 $params['loadBalancerCallback'] = static function () use ( $dbLbFactory ) {
260 return $dbLbFactory()->getMainLb();
261 };
262 $params += [ 'dbDomain' => false ];
263 }
264 $params += [ 'writeBatchSize' => $this->options->get( MainConfigNames::UpdateRowsPerQuery ) ];
265 }
266
267 private function prepareMemcachedBagOStuffFromParams( array &$params ): void {
268 $params += [
269 'servers' => $this->options->get( MainConfigNames::MemCachedServers ),
270 'persistent' => $this->options->get( MainConfigNames::MemCachedPersistent ),
271 'timeout' => $this->options->get( MainConfigNames::MemCachedTimeout ),
272 ];
273 }
274
275 private function prepareMultiWriteBagOStuffFromParams( array &$params ): void {
276 // Phan warns about foreach with non-array because it
277 // thinks any key can be Closure|IBufferingStatsdDataFactory
278 '@phan-var array{caches:array[]} $params';
279 foreach ( $params['caches'] ?? [] as $i => $cacheInfo ) {
280 // Ensure logger, keyspace, asyncHandler, etc are injected just as if
281 // one of these was configured without MultiWriteBagOStuff (T318272)
282 $params['caches'][$i] = $this->newFromParams( $cacheInfo );
283 }
284 }
285
303 $cache = $this->getInstance( CACHE_ACCEL );
304 if ( $cache instanceof EmptyBagOStuff ) {
305 if ( is_array( $fallback ) ) {
306 $fallback = $fallback['fallback'] ?? CACHE_NONE;
307 }
308 $cache = $this->getInstance( $fallback );
309 }
310
311 return $cache;
312 }
313
317 public function clear(): void {
318 $this->instances = [];
319 }
320
325 private static function getLocalServerCacheClass() {
326 if ( self::$localServerCacheClass !== null ) {
327 return self::$localServerCacheClass;
328 }
329 if ( function_exists( 'apcu_fetch' ) ) {
330 // Make sure the APCu methods actually store anything
331 if ( PHP_SAPI !== 'cli' || ini_get( 'apc.enable_cli' ) ) {
332 return APCUBagOStuff::class;
333
334 }
335 }
336
337 return EmptyBagOStuff::class;
338 }
339
346 public function getAnythingId() {
347 $candidates = [
348 $this->options->get( MainConfigNames::MainCacheType ),
349 $this->options->get( MainConfigNames::MessageCacheType ),
350 $this->options->get( MainConfigNames::ParserCacheType )
351 ];
352 foreach ( $candidates as $candidate ) {
353 if ( $candidate === CACHE_ACCEL ) {
354 // CACHE_ACCEL might default to nothing if no APCu
355 // See includes/ServiceWiring.php
356 $class = self::getLocalServerCacheClass();
357 if ( $class !== EmptyBagOStuff::class ) {
358 return $candidate;
359 }
360 } elseif ( $candidate !== CACHE_NONE && $candidate !== CACHE_ANYTHING ) {
361 return $candidate;
362 }
363 }
364
365 $services = MediaWikiServices::getInstance();
366
367 if ( $services->isServiceDisabled( 'DBLoadBalancer' ) ) {
368 // The DBLoadBalancer service is disabled, so we can't use the database!
369 $candidate = CACHE_NONE;
370 } elseif ( $services->isStorageDisabled() ) {
371 // Storage services are disabled because MediaWikiServices::disableStorage()
372 // was called. This is typically the case during installation.
373 $candidate = CACHE_NONE;
374 } else {
375 $candidate = CACHE_DB;
376 }
377 return $candidate;
378 }
379
399 public static function makeLocalServerCache( string $keyspace ) {
400 $params = [
401 'reportDupes' => false,
402 // Even simple caches must use a keyspace (T247562)
403 'keyspace' => $keyspace,
404 ];
405 $class = self::getLocalServerCacheClass();
406 return new $class( $params );
407 }
408
416 public function isDatabaseId( $id ) {
417 // NOTE: Sanity check if $id is set to CACHE_ANYTHING and
418 // everything is going through service wiring. CACHE_ANYTHING
419 // would default to CACHE_DB, let's handle that early for cases
420 // where all cache configs are set to CACHE_ANYTHING (T362686).
421 if ( $id === CACHE_ANYTHING ) {
422 $id = $this->getAnythingId();
423 return $this->isDatabaseId( $id );
424 }
425
426 if ( !isset( $this->options->get( MainConfigNames::ObjectCaches )[$id] ) ) {
427 return false;
428 }
429 $cache = $this->options->get( MainConfigNames::ObjectCaches )[$id];
430 if ( ( $cache['class'] ?? '' ) === SqlBagOStuff::class ) {
431 return true;
432 }
433
434 return false;
435 }
436
443 public function getLocalClusterInstance() {
444 return $this->getInstance(
445 $this->options->get( MainConfigNames::MainCacheType )
446 );
447 }
448}
449
451class_alias( ObjectCacheFactory::class, 'ObjectCacheFactory' );
const CACHE_NONE
Definition Defines.php:73
const CACHE_ANYTHING
Definition Defines.php:72
const CACHE_ACCEL
Definition Defines.php:76
const CACHE_HASH
Definition Defines.php:77
const CACHE_DB
Definition Defines.php:74
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
$fallback
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:69
A class for passing options to services.
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
Defer callable updates to run later in the PHP process.
A class containing constants representing the names of configuration variables.
const UpdateRowsPerQuery
Name constant for the UpdateRowsPerQuery setting, for use with Config::get()
const MainCacheType
Name constant for the MainCacheType setting, for use with Config::get()
const MemCachedPersistent
Name constant for the MemCachedPersistent setting, for use with Config::get()
const MemCachedTimeout
Name constant for the MemCachedTimeout setting, for use with Config::get()
const ObjectCaches
Name constant for the ObjectCaches setting, for use with Config::get()
const MemCachedServers
Name constant for the MemCachedServers setting, for use with Config::get()
const MessageCacheType
Name constant for the MessageCacheType setting, for use with Config::get()
const SQLiteDataDir
Name constant for the SQLiteDataDir setting, for use with Config::get()
const CachePrefix
Name constant for the CachePrefix setting, for use with Config::get()
const ParserCacheType
Name constant for the ParserCacheType setting, for use with Config::get()
Service locator for MediaWiki core services.
Factory for cache objects as configured in the ObjectCaches setting.
clear()
Clear all the cached instances.
static makeLocalServerCache(string $keyspace)
Create a new BagOStuff instance for local-server caching.
getInstance( $id)
Get a cached instance of the specified type of cache object.
__construct(ServiceOptions $options, StatsFactory $stats, Spi $loggerSpi, callable $dbLoadBalancerFactory, string $domainId, TracerInterface $telemetry)
static class string< BagOStuff > $localServerCacheClass
getLocalServerInstance( $fallback=CACHE_NONE)
Factory function for CACHE_ACCEL (referenced from configuration)
newFromParams(array $params)
Create a new cache object from parameters specification supplied.
getLocalClusterInstance()
Get the main cluster-local cache object.
getAnythingId()
Get the ID that will be used for CACHE_ANYTHING.
isDatabaseId( $id)
Determine whether a config ID would access the database.
Store data in the local server memory via APCu (php-apcu)
Abstract class for any ephemeral data store.
Definition BagOStuff.php:73
No-op implementation that stores nothing.
Store data in a memory for the current request/process only.
Store data in a memcached server or memcached cluster.
Wrap multiple BagOStuff objects, to implement different caching tiers.
This is the primary interface for validating metrics definitions, caching defined metrics,...
Service provider interface to create \Psr\Log\LoggerInterface objects.
Definition Spi.php:50
Base interface for an OpenTelemetry tracer responsible for creating spans.
array $params
The job parameters.