MediaWiki master
ObjectCacheFactory.php
Go to the documentation of this file.
1<?php
8
9use InvalidArgumentException;
23
66 public const CONSTRUCTOR_OPTIONS = [
76 ];
77
78 private ServiceOptions $options;
79 private StatsFactory $stats;
80 private Spi $logger;
81 private TracerInterface $telemetry;
83 private $instances = [];
84 private string $domainId;
86 private $dbLoadBalancerFactory;
92
93 public function __construct(
94 ServiceOptions $options,
95 StatsFactory $stats,
96 Spi $loggerSpi,
97 callable $dbLoadBalancerFactory,
98 string $domainId,
99 TracerInterface $telemetry
100 ) {
101 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
102 $this->options = $options;
103 $this->stats = $stats;
104 $this->logger = $loggerSpi;
105 $this->dbLoadBalancerFactory = $dbLoadBalancerFactory;
106 $this->domainId = $domainId;
107 $this->telemetry = $telemetry;
108 }
109
116 private function newFromId( $id ): BagOStuff {
117 if ( $id === CACHE_ANYTHING ) {
118 $id = $this->getAnythingId();
119 }
120
121 if ( !isset( $this->options->get( MainConfigNames::ObjectCaches )[$id] ) ) {
122 // Always recognize these
123 if ( $id === CACHE_NONE ) {
124 return new EmptyBagOStuff();
125 } elseif ( $id === CACHE_HASH ) {
126 return new HashBagOStuff();
127 } elseif ( $id === CACHE_ACCEL ) {
128 return self::makeLocalServerCache( $this->domainId );
129 } elseif ( $id === 'wincache' ) {
130 wfDeprecated( __METHOD__ . ' with cache ID "wincache"', '1.43' );
131 return self::makeLocalServerCache( $this->domainId );
132 }
133
134 throw new InvalidArgumentException( "Invalid object cache type \"$id\" requested. " .
135 "It is not present in \$wgObjectCaches." );
136 }
137
138 return $this->newFromParams( $this->options->get( MainConfigNames::ObjectCaches )[$id] );
139 }
140
147 public function getInstance( $id ): BagOStuff {
148 if ( !isset( $this->instances[$id] ) ) {
149 $this->instances[$id] = $this->newFromId( $id );
150 }
151
152 return $this->instances[$id];
153 }
154
172 public function newFromParams( array $params ): BagOStuff {
173 $logger = $this->logger->getLogger( $params['loggroup'] ?? 'objectcache' );
174 // Apply default parameters and resolve the logger instance
175 $params += [
176 'logger' => $logger,
177 'keyspace' => $this->domainId,
178 // T415142: Must be serializable and cannot use the ( ... ) syntax!
179 'asyncHandler' => [ DeferredUpdates::class, 'addCallableUpdate' ],
180 'reportDupes' => true,
181 'stats' => $this->stats,
182 'telemetry' => $this->telemetry,
183 ];
184
185 if ( isset( $params['factory'] ) ) {
186 $args = $params['args'] ?? [ $params ];
187
188 return $params['factory']( ...$args );
189 }
190
191 if ( !isset( $params['class'] ) ) {
192 throw new InvalidArgumentException(
193 'No "factory" nor "class" provided; got "' . print_r( $params, true ) . '"'
194 );
195 }
196
197 $class = $params['class'];
198
199 // Normalization and DI for SqlBagOStuff
200 if ( is_a( $class, SqlBagOStuff::class, true ) ) {
201 $this->prepareSqlBagOStuffFromParams( $params );
202 }
203
204 // Normalization and DI for MemcachedBagOStuff
205 if ( is_subclass_of( $class, MemcachedBagOStuff::class ) ) {
206 $this->prepareMemcachedBagOStuffFromParams( $params );
207 }
208
209 // Normalization and DI for MultiWriteBagOStuff
210 if ( is_a( $class, MultiWriteBagOStuff::class, true ) ) {
211 $this->prepareMultiWriteBagOStuffFromParams( $params );
212 }
213
214 return new $class( $params );
215 }
216
217 private function prepareSqlBagOStuffFromParams( array &$params ): void {
218 if ( isset( $params['globalKeyLB'] ) ) {
219 throw new InvalidArgumentException(
220 'globalKeyLB in $wgObjectCaches is no longer supported' );
221 }
222 if ( isset( $params['server'] ) && !isset( $params['servers'] ) ) {
223 $params['servers'] = [ $params['server'] ];
224 unset( $params['server'] );
225 }
226 if ( isset( $params['servers'] ) ) {
227 // In the past it was not required to set 'dbDirectory' in $wgObjectCaches
228 foreach ( $params['servers'] as &$server ) {
229 if ( $server['type'] === 'sqlite' && !isset( $server['dbDirectory'] ) ) {
230 $server['dbDirectory'] = $this->options->get( MainConfigNames::SQLiteDataDir );
231 }
232 }
233 } elseif ( isset( $params['cluster'] ) ) {
234 $cluster = $params['cluster'];
235 $dbLbFactory = $this->dbLoadBalancerFactory;
236 $params['loadBalancerCallback'] = static function () use ( $cluster, $dbLbFactory ) {
237 return $dbLbFactory()->getExternalLB( $cluster );
238 };
239 $params += [ 'dbDomain' => false ];
240 } else {
241 $dbLbFactory = $this->dbLoadBalancerFactory;
242 $params['loadBalancerCallback'] = static function () use ( $dbLbFactory ) {
243 return $dbLbFactory()->getMainLb();
244 };
245 $params += [ 'dbDomain' => false ];
246 }
247 $params += [ 'writeBatchSize' => $this->options->get( MainConfigNames::UpdateRowsPerQuery ) ];
248 }
249
250 private function prepareMemcachedBagOStuffFromParams( array &$params ): void {
251 $params += [
252 'servers' => $this->options->get( MainConfigNames::MemCachedServers ),
253 'persistent' => $this->options->get( MainConfigNames::MemCachedPersistent ),
254 'timeout' => $this->options->get( MainConfigNames::MemCachedTimeout ),
255 ];
256 }
257
258 private function prepareMultiWriteBagOStuffFromParams( array &$params ): void {
259 // Phan warns about foreach with non-array because it
260 // thinks any key can be Closure|IBufferingStatsdDataFactory
261 '@phan-var array{caches:array[]} $params';
262 foreach ( $params['caches'] ?? [] as $i => $cacheInfo ) {
263 // Ensure logger, keyspace, asyncHandler, etc are injected just as if
264 // one of these was configured without MultiWriteBagOStuff (T318272)
265 $params['caches'][$i] = $this->newFromParams( $cacheInfo );
266 }
267 }
268
286 $cache = $this->getInstance( CACHE_ACCEL );
287 if ( $cache instanceof EmptyBagOStuff ) {
288 if ( is_array( $fallback ) ) {
289 $fallback = $fallback['fallback'] ?? CACHE_NONE;
290 }
291 $cache = $this->getInstance( $fallback );
292 }
293
294 return $cache;
295 }
296
300 public function clear(): void {
301 $this->instances = [];
302 }
303
308 private static function getLocalServerCacheClass() {
309 if ( self::$localServerCacheClass !== null ) {
310 return self::$localServerCacheClass;
311 }
312 if ( function_exists( 'apcu_fetch' ) ) {
313 // Make sure the APCu methods actually store anything
314 if ( PHP_SAPI !== 'cli' || ini_get( 'apc.enable_cli' ) ) {
315 return APCUBagOStuff::class;
316 }
317 }
318
319 return EmptyBagOStuff::class;
320 }
321
328 public function getAnythingId() {
329 $candidates = [
330 $this->options->get( MainConfigNames::MainCacheType ),
331 $this->options->get( MainConfigNames::MessageCacheType ),
332 $this->options->get( MainConfigNames::ParserCacheType )
333 ];
334 foreach ( $candidates as $candidate ) {
335 if ( $candidate === CACHE_ACCEL ) {
336 // CACHE_ACCEL might default to nothing if no APCu
337 // See includes/ServiceWiring.php
338 $class = self::getLocalServerCacheClass();
339 if ( $class !== EmptyBagOStuff::class ) {
340 return $candidate;
341 }
342 } elseif ( $candidate !== CACHE_NONE && $candidate !== CACHE_ANYTHING ) {
343 return $candidate;
344 }
345 }
346
347 $services = MediaWikiServices::getInstance();
348
349 if ( $services->isServiceDisabled( 'DBLoadBalancer' ) ) {
350 // The DBLoadBalancer service is disabled, so we can't use the database!
351 $candidate = CACHE_NONE;
352 } elseif ( $services->isStorageDisabled() ) {
353 // Storage services are disabled because MediaWikiServices::disableStorage()
354 // was called. This is typically the case during installation.
355 $candidate = CACHE_NONE;
356 } else {
357 $candidate = CACHE_DB;
358 }
359 return $candidate;
360 }
361
381 public static function makeLocalServerCache( string $keyspace ) {
382 $params = [
383 'reportDupes' => false,
384 // Even simple caches must use a keyspace (T247562)
385 'keyspace' => $keyspace,
386 ];
387 $class = self::getLocalServerCacheClass();
388 return new $class( $params );
389 }
390
398 public function isDatabaseId( $id ) {
399 // NOTE: Sanity check if $id is set to CACHE_ANYTHING and
400 // everything is going through service wiring. CACHE_ANYTHING
401 // would default to CACHE_DB, let's handle that early for cases
402 // where all cache configs are set to CACHE_ANYTHING (T362686).
403 if ( $id === CACHE_ANYTHING ) {
404 $id = $this->getAnythingId();
405 return $this->isDatabaseId( $id );
406 }
407
408 if ( !isset( $this->options->get( MainConfigNames::ObjectCaches )[$id] ) ) {
409 return false;
410 }
411 $cache = $this->options->get( MainConfigNames::ObjectCaches )[$id];
412 if ( ( $cache['class'] ?? '' ) === SqlBagOStuff::class ) {
413 return true;
414 }
415
416 return false;
417 }
418
425 public function getLocalClusterInstance() {
426 return $this->getInstance(
427 $this->options->get( MainConfigNames::MainCacheType )
428 );
429 }
430}
431
433class_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:71
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 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.