MediaWiki master
ObjectCacheFactory.php
Go to the documentation of this file.
1<?php
34
77 public const CONSTRUCTOR_OPTIONS = [
78 MainConfigNames::SQLiteDataDir,
79 MainConfigNames::UpdateRowsPerQuery,
80 MainConfigNames::MemCachedServers,
81 MainConfigNames::MemCachedPersistent,
82 MainConfigNames::MemCachedTimeout,
83 MainConfigNames::CachePrefix,
84 MainConfigNames::ObjectCaches,
85 MainConfigNames::MainCacheType,
86 MainConfigNames::MessageCacheType,
87 MainConfigNames::ParserCacheType,
88 ];
89
90 private ServiceOptions $options;
91 private StatsFactory $stats;
92 private Spi $logger;
93 private TracerInterface $telemetry;
95 private $instances = [];
96 private string $domainId;
98 private $dbLoadBalancerFactory;
104
105 public function __construct(
106 ServiceOptions $options,
107 StatsFactory $stats,
108 Spi $loggerSpi,
109 callable $dbLoadBalancerFactory,
110 string $domainId,
111 TracerInterface $telemetry
112 ) {
113 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
114 $this->options = $options;
115 $this->stats = $stats;
116 $this->logger = $loggerSpi;
117 $this->dbLoadBalancerFactory = $dbLoadBalancerFactory;
118 $this->domainId = $domainId;
119 $this->telemetry = $telemetry;
120 }
121
129 private function getDefaultKeyspace(): string {
130 $cachePrefix = $this->options->get( MainConfigNames::CachePrefix );
131 if ( is_string( $cachePrefix ) && $cachePrefix !== '' ) {
132 return $cachePrefix;
133 }
134
135 return $this->domainId;
136 }
137
144 private function newFromId( $id ): BagOStuff {
145 if ( $id === CACHE_ANYTHING ) {
146 $id = $this->getAnythingId();
147 }
148
149 if ( !isset( $this->options->get( MainConfigNames::ObjectCaches )[$id] ) ) {
150 // Always recognize these
151 if ( $id === CACHE_NONE ) {
152 return new EmptyBagOStuff();
153 } elseif ( $id === CACHE_HASH ) {
154 return new HashBagOStuff();
155 } elseif ( $id === CACHE_ACCEL ) {
156 return self::makeLocalServerCache( $this->getDefaultKeyspace() );
157 } elseif ( $id === 'wincache' ) {
158 wfDeprecated( __METHOD__ . ' with cache ID "wincache"', '1.43' );
159 return self::makeLocalServerCache( $this->getDefaultKeyspace() );
160 }
161
162 throw new InvalidArgumentException( "Invalid object cache type \"$id\" requested. " .
163 "It is not present in \$wgObjectCaches." );
164 }
165
166 return $this->newFromParams( $this->options->get( MainConfigNames::ObjectCaches )[$id] );
167 }
168
175 public function getInstance( $id ): BagOStuff {
176 if ( !isset( $this->instances[$id] ) ) {
177 $this->instances[$id] = $this->newFromId( $id );
178 }
179
180 return $this->instances[$id];
181 }
182
198 public function newFromParams( array $params ): BagOStuff {
199 $logger = $this->logger->getLogger( $params['loggroup'] ?? 'objectcache' );
200 // Apply default parameters and resolve the logger instance
201 $params += [
202 'logger' => $logger,
203 'keyspace' => $this->getDefaultKeyspace(),
204 'asyncHandler' => [ DeferredUpdates::class, 'addCallableUpdate' ],
205 'reportDupes' => true,
206 'stats' => $this->stats,
207 'telemetry' => $this->telemetry,
208 ];
209
210 if ( isset( $params['factory'] ) ) {
211 $args = $params['args'] ?? [ $params ];
212
213 return $params['factory']( ...$args );
214 }
215
216 if ( !isset( $params['class'] ) ) {
217 throw new InvalidArgumentException(
218 'No "factory" nor "class" provided; got "' . print_r( $params, true ) . '"'
219 );
220 }
221
222 $class = $params['class'];
223
224 // Normalization and DI for SqlBagOStuff
225 if ( is_a( $class, SqlBagOStuff::class, true ) ) {
226 $this->prepareSqlBagOStuffFromParams( $params );
227 }
228
229 // Normalization and DI for MemcachedBagOStuff
230 if ( is_subclass_of( $class, MemcachedBagOStuff::class ) ) {
231 $this->prepareMemcachedBagOStuffFromParams( $params );
232 }
233
234 // Normalization and DI for MultiWriteBagOStuff
235 if ( is_a( $class, MultiWriteBagOStuff::class, true ) ) {
236 $this->prepareMultiWriteBagOStuffFromParams( $params );
237 }
238
239 return new $class( $params );
240 }
241
242 private function prepareSqlBagOStuffFromParams( array &$params ): void {
243 if ( isset( $params['globalKeyLB'] ) ) {
244 throw new InvalidArgumentException(
245 'globalKeyLB in $wgObjectCaches is no longer supported' );
246 }
247 if ( isset( $params['server'] ) && !isset( $params['servers'] ) ) {
248 $params['servers'] = [ $params['server'] ];
249 unset( $params['server'] );
250 }
251 if ( isset( $params['servers'] ) ) {
252 // In the past it was not required to set 'dbDirectory' in $wgObjectCaches
253 foreach ( $params['servers'] as &$server ) {
254 if ( $server['type'] === 'sqlite' && !isset( $server['dbDirectory'] ) ) {
255 $server['dbDirectory'] = $this->options->get( MainConfigNames::SQLiteDataDir );
256 }
257 }
258 } elseif ( isset( $params['cluster'] ) ) {
259 $cluster = $params['cluster'];
260 $dbLbFactory = $this->dbLoadBalancerFactory;
261 $params['loadBalancerCallback'] = static function () use ( $cluster, $dbLbFactory ) {
262 return $dbLbFactory()->getExternalLB( $cluster );
263 };
264 $params += [ 'dbDomain' => false ];
265 } else {
266 $dbLbFactory = $this->dbLoadBalancerFactory;
267 $params['loadBalancerCallback'] = static function () use ( $dbLbFactory ) {
268 return $dbLbFactory()->getMainLb();
269 };
270 $params += [ 'dbDomain' => false ];
271 }
272 $params += [ 'writeBatchSize' => $this->options->get( MainConfigNames::UpdateRowsPerQuery ) ];
273 }
274
275 private function prepareMemcachedBagOStuffFromParams( array &$params ): void {
276 $params += [
277 'servers' => $this->options->get( MainConfigNames::MemCachedServers ),
278 'persistent' => $this->options->get( MainConfigNames::MemCachedPersistent ),
279 'timeout' => $this->options->get( MainConfigNames::MemCachedTimeout ),
280 ];
281 }
282
283 private function prepareMultiWriteBagOStuffFromParams( array &$params ): void {
284 // Phan warns about foreach with non-array because it
285 // thinks any key can be Closure|IBufferingStatsdDataFactory
286 '@phan-var array{caches:array[]} $params';
287 foreach ( $params['caches'] ?? [] as $i => $cacheInfo ) {
288 // Ensure logger, keyspace, asyncHandler, etc are injected just as if
289 // one of these was configured without MultiWriteBagOStuff (T318272)
290 $params['caches'][$i] = $this->newFromParams( $cacheInfo );
291 }
292 }
293
311 $cache = $this->getInstance( CACHE_ACCEL );
312 if ( $cache instanceof EmptyBagOStuff ) {
313 if ( is_array( $fallback ) ) {
314 $fallback = $fallback['fallback'] ?? CACHE_NONE;
315 }
316 $cache = $this->getInstance( $fallback );
317 }
318
319 return $cache;
320 }
321
325 public function clear(): void {
326 $this->instances = [];
327 }
328
333 private static function getLocalServerCacheClass() {
334 if ( self::$localServerCacheClass !== null ) {
335 return self::$localServerCacheClass;
336 }
337 if ( function_exists( 'apcu_fetch' ) ) {
338 // Make sure the APCu methods actually store anything
339 if ( PHP_SAPI !== 'cli' || ini_get( 'apc.enable_cli' ) ) {
340 return APCUBagOStuff::class;
341
342 }
343 }
344
345 return EmptyBagOStuff::class;
346 }
347
354 public function getAnythingId() {
355 $candidates = [
356 $this->options->get( MainConfigNames::MainCacheType ),
357 $this->options->get( MainConfigNames::MessageCacheType ),
358 $this->options->get( MainConfigNames::ParserCacheType )
359 ];
360 foreach ( $candidates as $candidate ) {
361 if ( $candidate === CACHE_ACCEL ) {
362 // CACHE_ACCEL might default to nothing if no APCu
363 // See includes/ServiceWiring.php
364 $class = self::getLocalServerCacheClass();
365 if ( $class !== EmptyBagOStuff::class ) {
366 return $candidate;
367 }
368 } elseif ( $candidate !== CACHE_NONE && $candidate !== CACHE_ANYTHING ) {
369 return $candidate;
370 }
371 }
372
373 $services = MediaWikiServices::getInstance();
374
375 if ( $services->isServiceDisabled( 'DBLoadBalancer' ) ) {
376 // The DBLoadBalancer service is disabled, so we can't use the database!
377 $candidate = CACHE_NONE;
378 } elseif ( $services->isStorageDisabled() ) {
379 // Storage services are disabled because MediaWikiServices::disableStorage()
380 // was called. This is typically the case during installation.
381 $candidate = CACHE_NONE;
382 } else {
383 $candidate = CACHE_DB;
384 }
385 return $candidate;
386 }
387
407 public static function makeLocalServerCache( string $keyspace ) {
408 $params = [
409 'reportDupes' => false,
410 // Even simple caches must use a keyspace (T247562)
411 'keyspace' => $keyspace,
412 ];
413 $class = self::getLocalServerCacheClass();
414 return new $class( $params );
415 }
416
424 public function isDatabaseId( $id ) {
425 // NOTE: Sanity check if $id is set to CACHE_ANYTHING and
426 // everything is going through service wiring. CACHE_ANYTHING
427 // would default to CACHE_DB, let's handle that early for cases
428 // where all cache configs are set to CACHE_ANYTHING (T362686).
429 if ( $id === CACHE_ANYTHING ) {
430 $id = $this->getAnythingId();
431 return $this->isDatabaseId( $id );
432 }
433
434 if ( !isset( $this->options->get( MainConfigNames::ObjectCaches )[$id] ) ) {
435 return false;
436 }
437 $cache = $this->options->get( MainConfigNames::ObjectCaches )[$id];
438 if ( ( $cache['class'] ?? '' ) === SqlBagOStuff::class ) {
439 return true;
440 }
441
442 return false;
443 }
444
451 public function getLocalClusterInstance() {
452 return $this->getInstance(
453 $this->options->get( MainConfigNames::MainCacheType )
454 );
455 }
456}
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
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:82
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.
Service locator for MediaWiki core services.
Factory for cache objects as configured in the ObjectCaches setting.
__construct(ServiceOptions $options, StatsFactory $stats, Spi $loggerSpi, callable $dbLoadBalancerFactory, string $domainId, TracerInterface $telemetry)
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.
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.
Store data in the local server memory via APCu (php-apcu)
Abstract class for any ephemeral data store.
Definition BagOStuff.php:87
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:64
Base interface for an OpenTelemetry tracer responsible for creating spans.
array $params
The job parameters.