MediaWiki master
ObjectCacheFactory.php
Go to the documentation of this file.
1<?php
36
80 public const CONSTRUCTOR_OPTIONS = [
81 MainConfigNames::SQLiteDataDir,
82 MainConfigNames::UpdateRowsPerQuery,
83 MainConfigNames::MemCachedServers,
84 MainConfigNames::MemCachedPersistent,
85 MainConfigNames::MemCachedTimeout,
86 MainConfigNames::CachePrefix,
87 MainConfigNames::ObjectCaches,
88 MainConfigNames::MainCacheType,
89 MainConfigNames::MessageCacheType,
90 MainConfigNames::ParserCacheType,
91 ];
92
93 private ServiceOptions $options;
94 private StatsFactory $stats;
95 private Spi $logger;
97 private $instances = [];
98 private string $domainId;
100 private $dbLoadBalancerFactory;
106
107 public function __construct(
108 ServiceOptions $options,
109 StatsFactory $stats,
110 Spi $loggerSpi,
111 callable $dbLoadBalancerFactory,
112 string $domainId
113 ) {
114 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
115 $this->options = $options;
116 $this->stats = $stats;
117 $this->logger = $loggerSpi;
118 $this->dbLoadBalancerFactory = $dbLoadBalancerFactory;
119 $this->domainId = $domainId;
120 }
121
131 private function getDefaultKeyspace(): string {
132 $cachePrefix = $this->options->get( MainConfigNames::CachePrefix );
133 if ( is_string( $cachePrefix ) && $cachePrefix !== '' ) {
134 return $cachePrefix;
135 }
136
137 return $this->domainId;
138 }
139
146 private function newFromId( $id ): BagOStuff {
147 if ( $id === CACHE_ANYTHING ) {
148 $id = $this->getAnythingId();
149 }
150
151 if ( !isset( $this->options->get( MainConfigNames::ObjectCaches )[$id] ) ) {
152 // Always recognize these
153 if ( $id === CACHE_NONE ) {
154 return new EmptyBagOStuff();
155 } elseif ( $id === CACHE_HASH ) {
156 return new HashBagOStuff();
157 } elseif ( $id === CACHE_ACCEL ) {
158 return self::makeLocalServerCache( $this->getDefaultKeyspace() );
159 }
160
161 throw new InvalidArgumentException( "Invalid object cache type \"$id\" requested. " .
162 "It is not present in \$wgObjectCaches." );
163 }
164
165 return $this->newFromParams( $this->options->get( MainConfigNames::ObjectCaches )[$id] );
166 }
167
174 public function getInstance( $id ): BagOStuff {
175 if ( !isset( $this->instances[$id] ) ) {
176 $this->instances[$id] = $this->newFromId( $id );
177 }
178
179 return $this->instances[$id];
180 }
181
197 public function newFromParams( array $params ): BagOStuff {
198 $logger = $this->logger->getLogger( $params['loggroup'] ?? 'objectcache' );
199 // Apply default parameters and resolve the logger instance
200 $params += [
201 'logger' => $logger,
202 'keyspace' => $this->getDefaultKeyspace(),
203 'asyncHandler' => [ DeferredUpdates::class, 'addCallableUpdate' ],
204 'reportDupes' => true,
205 'stats' => $this->stats,
206 ];
207
208 if ( isset( $params['factory'] ) ) {
209 $args = $params['args'] ?? [ $params ];
210
211 return call_user_func( $params['factory'], ...$args );
212 }
213
214 if ( !isset( $params['class'] ) ) {
215 throw new InvalidArgumentException(
216 'No "factory" nor "class" provided; got "' . print_r( $params, true ) . '"'
217 );
218 }
219
220 $class = $params['class'];
221
222 // Normalization and DI for SqlBagOStuff
223 if ( is_a( $class, SqlBagOStuff::class, true ) ) {
224 $this->prepareSqlBagOStuffFromParams( $params );
225 }
226
227 // Normalization and DI for MemcachedBagOStuff
228 if ( is_subclass_of( $class, MemcachedBagOStuff::class ) ) {
229 $this->prepareMemcachedBagOStuffFromParams( $params );
230 }
231
232 // Normalization and DI for MultiWriteBagOStuff
233 if ( is_a( $class, MultiWriteBagOStuff::class, true ) ) {
234 $this->prepareMultiWriteBagOStuffFromParams( $params );
235 }
236 if ( is_a( $class, RESTBagOStuff::class, true ) ) {
237 $this->prepareRESTBagOStuffFromParams( $params );
238 }
239
240 return new $class( $params );
241 }
242
243 private function prepareSqlBagOStuffFromParams( array &$params ): void {
244 if ( isset( $params['globalKeyLB'] ) ) {
245 throw new InvalidArgumentException(
246 'globalKeyLB in $wgObjectCaches is no longer supported' );
247 }
248 if ( isset( $params['server'] ) && !isset( $params['servers'] ) ) {
249 $params['servers'] = [ $params['server'] ];
250 unset( $params['server'] );
251 }
252 if ( isset( $params['servers'] ) ) {
253 // In the past it was not required to set 'dbDirectory' in $wgObjectCaches
254 foreach ( $params['servers'] as &$server ) {
255 if ( $server['type'] === 'sqlite' && !isset( $server['dbDirectory'] ) ) {
256 $server['dbDirectory'] = $this->options->get( MainConfigNames::SQLiteDataDir );
257 }
258 }
259 } elseif ( isset( $params['cluster'] ) ) {
260 $cluster = $params['cluster'];
261 $dbLbFactory = $this->dbLoadBalancerFactory;
262 $params['loadBalancerCallback'] = static function () use ( $cluster, $dbLbFactory ) {
263 return $dbLbFactory()->getExternalLB( $cluster );
264 };
265 $params += [ 'dbDomain' => false ];
266 } else {
267 $dbLbFactory = $this->dbLoadBalancerFactory;
268 $params['loadBalancerCallback'] = static function () use ( $dbLbFactory ) {
269 return $dbLbFactory()->getMainLb();
270 };
271 $params += [ 'dbDomain' => false ];
272 }
273 $params += [ 'writeBatchSize' => $this->options->get( MainConfigNames::UpdateRowsPerQuery ) ];
274 }
275
276 private function prepareMemcachedBagOStuffFromParams( array &$params ): void {
277 $params += [
278 'servers' => $this->options->get( MainConfigNames::MemCachedServers ),
279 'persistent' => $this->options->get( MainConfigNames::MemCachedPersistent ),
280 'timeout' => $this->options->get( MainConfigNames::MemCachedTimeout ),
281 ];
282 }
283
284 private function prepareMultiWriteBagOStuffFromParams( array &$params ): void {
285 // Phan warns about foreach with non-array because it
286 // thinks any key can be Closure|IBufferingStatsdDataFactory
287 '@phan-var array{caches:array[]} $params';
288 foreach ( $params['caches'] ?? [] as $i => $cacheInfo ) {
289 // Ensure logger, keyspace, asyncHandler, etc are injected just as if
290 // one of these was configured without MultiWriteBagOStuff.
291 $params['caches'][$i] = $this->newFromParams( $cacheInfo );
292 }
293 }
294
295 private function prepareRESTBagOStuffFromParams( array &$params ): void {
296 $params['telemetry'] = Telemetry::getInstance();
297 }
298
316 $cache = $this->getInstance( CACHE_ACCEL );
317 if ( $cache instanceof EmptyBagOStuff ) {
318 if ( is_array( $fallback ) ) {
319 $fallback = $fallback['fallback'] ?? CACHE_NONE;
320 }
321 $cache = $this->getInstance( $fallback );
322 }
323
324 return $cache;
325 }
326
330 public function clear(): void {
331 $this->instances = [];
332 }
333
338 private static function getLocalServerCacheClass() {
339 if ( self::$localServerCacheClass !== null ) {
340 return self::$localServerCacheClass;
341 }
342 if ( function_exists( 'apcu_fetch' ) ) {
343 // Make sure the APCu methods actually store anything
344 if ( PHP_SAPI !== 'cli' || ini_get( 'apc.enable_cli' ) ) {
345 return APCUBagOStuff::class;
346
347 }
348 } elseif ( function_exists( 'wincache_ucache_get' ) ) {
349 return WinCacheBagOStuff::class;
350 }
351
352 return EmptyBagOStuff::class;
353 }
354
361 public function getAnythingId() {
362 $candidates = [
363 $this->options->get( MainConfigNames::MainCacheType ),
364 $this->options->get( MainConfigNames::MessageCacheType ),
365 $this->options->get( MainConfigNames::ParserCacheType )
366 ];
367 foreach ( $candidates as $candidate ) {
368 if ( $candidate === CACHE_ACCEL ) {
369 // CACHE_ACCEL might default to nothing if no APCu
370 // See includes/ServiceWiring.php
371 $class = self::getLocalServerCacheClass();
372 if ( $class !== EmptyBagOStuff::class ) {
373 return $candidate;
374 }
375 } elseif ( $candidate !== CACHE_NONE && $candidate !== CACHE_ANYTHING ) {
376 return $candidate;
377 }
378 }
379
380 $services = MediaWikiServices::getInstance();
381
382 if ( $services->isServiceDisabled( 'DBLoadBalancer' ) ) {
383 // The DBLoadBalancer service is disabled, so we can't use the database!
384 $candidate = CACHE_NONE;
385 } elseif ( $services->isStorageDisabled() ) {
386 // Storage services are disabled because MediaWikiServices::disableStorage()
387 // was called. This is typically the case during installation.
388 $candidate = CACHE_NONE;
389 } else {
390 $candidate = CACHE_DB;
391 }
392 return $candidate;
393 }
394
414 public static function makeLocalServerCache( string $keyspace ) {
415 $params = [
416 'reportDupes' => false,
417 // Even simple caches must use a keyspace (T247562)
418 'keyspace' => $keyspace,
419 ];
420 $class = self::getLocalServerCacheClass();
421 return new $class( $params );
422 }
423
431 public function isDatabaseId( $id ) {
432 // NOTE: Sanity check if $id is set to CACHE_ANYTHING and
433 // everything is going through service wiring. CACHE_ANYTHING
434 // would default to CACHE_DB, let's handle that early for cases
435 // where all cache configs are set to CACHE_ANYTHING (T362686).
436 if ( $id === CACHE_ANYTHING ) {
437 $id = $this->getAnythingId();
438 return $this->isDatabaseId( $id );
439 }
440
441 if ( !isset( $this->options->get( MainConfigNames::ObjectCaches )[$id] ) ) {
442 return false;
443 }
444 $cache = $this->options->get( MainConfigNames::ObjectCaches )[$id];
445 if ( ( $cache['class'] ?? '' ) === SqlBagOStuff::class ) {
446 return true;
447 }
448
449 return false;
450 }
451
458 public function getLocalClusterInstance() {
459 return $this->getInstance(
460 $this->options->get( MainConfigNames::MainCacheType )
461 );
462 }
463}
const CACHE_NONE
Definition Defines.php:87
const CACHE_ANYTHING
Definition Defines.php:86
const CACHE_ACCEL
Definition Defines.php:90
const CACHE_HASH
Definition Defines.php:91
const CACHE_DB
Definition Defines.php:88
$fallback
array $params
The job parameters.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:81
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.
Service for handling telemetry data.
Definition Telemetry.php:29
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
Factory for cache objects as configured in the ObjectCaches setting.
isDatabaseId( $id)
Determine whether a config ID would access the database.
getLocalClusterInstance()
Get the main cluster-local cache object.
getInstance( $id)
Get a cached instance of the specified type of cache object.
__construct(ServiceOptions $options, StatsFactory $stats, Spi $loggerSpi, callable $dbLoadBalancerFactory, string $domainId)
getLocalServerInstance( $fallback=CACHE_NONE)
Factory function for CACHE_ACCEL (referenced from configuration)
static string $localServerCacheClass
getAnythingId()
Get the ID that will be used for CACHE_ANYTHING.
newFromParams(array $params)
Create a new cache object from parameters specification supplied.
static makeLocalServerCache(string $keyspace)
Create a new BagOStuff instance for local-server caching.
clear()
Clear all the cached instances.
This is a wrapper for APCu's shared memory functions.
Class representing a cache/ephemeral data store.
Definition BagOStuff.php:88
A BagOStuff object with no objects in it.
Simple store for keeping values in an associative array for the current process.
Base class for memcached clients.
A cache class that replicates all writes to multiple child caches.
Interface to key-value storage behind an HTTP server.
Wrapper for WinCache object caching functions; identical interface to the APC wrapper.
StatsFactory Implementation.
Service provider interface to create \Psr\Log\LoggerInterface objects.
Definition Spi.php:64