ObjectCache.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. <?php
  2. /**
  3. * Functions to get cache objects.
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License along
  16. * with this program; if not, write to the Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. * http://www.gnu.org/copyleft/gpl.html
  19. *
  20. * @file
  21. * @ingroup Cache
  22. */
  23. use MediaWiki\Logger\LoggerFactory;
  24. use MediaWiki\MediaWikiServices;
  25. /**
  26. * Functions to get cache objects
  27. *
  28. * The word "cache" has two main dictionary meanings, and both
  29. * are used in this factory class. They are:
  30. *
  31. * - a) Cache (the computer science definition).
  32. * A place to store copies or computations on existing data for
  33. * higher access speeds.
  34. * - b) Storage.
  35. * A place to store lightweight data that is not canonically
  36. * stored anywhere else (e.g. a "hoard" of objects).
  37. *
  38. * The former should always use strongly consistent stores, so callers don't
  39. * have to deal with stale reads. The latter may be eventually consistent, but
  40. * callers can use BagOStuff:READ_LATEST to see the latest available data.
  41. *
  42. * Primary entry points:
  43. *
  44. * - ObjectCache::getLocalServerInstance( $fallbackType )
  45. * Purpose: Memory cache for very hot keys.
  46. * Stored only on the individual web server (typically APC or APCu for web requests,
  47. * and EmptyBagOStuff in CLI mode).
  48. * Not replicated to the other servers.
  49. *
  50. * - ObjectCache::getLocalClusterInstance()
  51. * Purpose: Memory storage for per-cluster coordination and tracking.
  52. * A typical use case would be a rate limit counter or cache regeneration mutex.
  53. * Stored centrally within the local data-center. Not replicated to other DCs.
  54. * Configured by $wgMainCacheType.
  55. *
  56. * - ObjectCache::getInstance( $cacheType )
  57. * Purpose: Special cases (like tiered memory/disk caches).
  58. * Get a specific cache type by key in $wgObjectCaches.
  59. *
  60. * All the above cache instances (BagOStuff and WANObjectCache) have their makeKey()
  61. * method scoped to the *current* wiki ID. Use makeGlobalKey() to avoid this scoping
  62. * when using keys that need to be shared amongst wikis.
  63. *
  64. * @ingroup Cache
  65. */
  66. class ObjectCache {
  67. /** @var BagOStuff[] Map of (id => BagOStuff) */
  68. public static $instances = [];
  69. /** @var WANObjectCache[] Map of (id => WANObjectCache) */
  70. public static $wanInstances = [];
  71. /**
  72. * Get a cached instance of the specified type of cache object.
  73. *
  74. * @param string $id A key in $wgObjectCaches.
  75. * @return BagOStuff
  76. */
  77. public static function getInstance( $id ) {
  78. if ( !isset( self::$instances[$id] ) ) {
  79. self::$instances[$id] = self::newFromId( $id );
  80. }
  81. return self::$instances[$id];
  82. }
  83. /**
  84. * Get a cached instance of the specified type of WAN cache object.
  85. *
  86. * @since 1.26
  87. * @param string $id A key in $wgWANObjectCaches.
  88. * @return WANObjectCache
  89. * @deprecated since 1.34 Use MediaWikiServices::getMainWANObjectCache instead
  90. */
  91. public static function getWANInstance( $id ) {
  92. wfDeprecated( __METHOD__, '1.34' );
  93. if ( !isset( self::$wanInstances[$id] ) ) {
  94. self::$wanInstances[$id] = self::newWANCacheFromId( $id );
  95. }
  96. return self::$wanInstances[$id];
  97. }
  98. /**
  99. * Create a new cache object of the specified type.
  100. *
  101. * @param string $id A key in $wgObjectCaches.
  102. * @return BagOStuff
  103. * @throws InvalidArgumentException
  104. */
  105. private static function newFromId( $id ) {
  106. global $wgObjectCaches;
  107. if ( !isset( $wgObjectCaches[$id] ) ) {
  108. // Always recognize these ones
  109. if ( $id === CACHE_NONE ) {
  110. return new EmptyBagOStuff();
  111. } elseif ( $id === 'hash' ) {
  112. return new HashBagOStuff();
  113. }
  114. throw new InvalidArgumentException( "Invalid object cache type \"$id\" requested. " .
  115. "It is not present in \$wgObjectCaches." );
  116. }
  117. return self::newFromParams( $wgObjectCaches[$id] );
  118. }
  119. /**
  120. * Get the default keyspace for this wiki.
  121. *
  122. * This is either the value of the `CachePrefix` configuration variable,
  123. * or (if the former is unset) the `DBname` configuration variable, with
  124. * `DBprefix` (if defined).
  125. *
  126. * @return string
  127. */
  128. private static function getDefaultKeyspace() {
  129. global $wgCachePrefix;
  130. $keyspace = $wgCachePrefix;
  131. if ( is_string( $keyspace ) && $keyspace !== '' ) {
  132. return $keyspace;
  133. }
  134. return WikiMap::getCurrentWikiDbDomain()->getId();
  135. }
  136. /**
  137. * Create a new cache object from parameters.
  138. *
  139. * @param array $params Must have 'factory' or 'class' property.
  140. * - factory: Callback passed $params that returns BagOStuff.
  141. * - class: BagOStuff subclass constructed with $params.
  142. * - loggroup: Alias to set 'logger' key with LoggerFactory group.
  143. * - .. Other parameters passed to factory or class.
  144. * @return BagOStuff
  145. * @throws InvalidArgumentException
  146. */
  147. public static function newFromParams( $params ) {
  148. $params['logger'] = $params['logger'] ??
  149. LoggerFactory::getInstance( $params['loggroup'] ?? 'objectcache' );
  150. if ( !isset( $params['keyspace'] ) ) {
  151. $params['keyspace'] = self::getDefaultKeyspace();
  152. }
  153. if ( isset( $params['factory'] ) ) {
  154. return call_user_func( $params['factory'], $params );
  155. } elseif ( isset( $params['class'] ) ) {
  156. $class = $params['class'];
  157. // Automatically set the 'async' update handler
  158. $params['asyncHandler'] = $params['asyncHandler']
  159. ?? [ DeferredUpdates::class, 'addCallableUpdate' ];
  160. // Enable reportDupes by default
  161. $params['reportDupes'] = $params['reportDupes'] ?? true;
  162. // Do b/c logic for SqlBagOStuff
  163. if ( is_a( $class, SqlBagOStuff::class, true ) ) {
  164. if ( isset( $params['server'] ) && !isset( $params['servers'] ) ) {
  165. $params['servers'] = [ $params['server'] ];
  166. unset( $params['server'] );
  167. }
  168. // In the past it was not required to set 'dbDirectory' in $wgObjectCaches
  169. if ( isset( $params['servers'] ) ) {
  170. foreach ( $params['servers'] as &$server ) {
  171. if ( $server['type'] === 'sqlite' && !isset( $server['dbDirectory'] ) ) {
  172. $server['dbDirectory'] = MediaWikiServices::getInstance()
  173. ->getMainConfig()->get( 'SQLiteDataDir' );
  174. }
  175. }
  176. }
  177. }
  178. // Do b/c logic for MemcachedBagOStuff
  179. if ( is_subclass_of( $class, MemcachedBagOStuff::class ) ) {
  180. if ( !isset( $params['servers'] ) ) {
  181. $params['servers'] = $GLOBALS['wgMemCachedServers'];
  182. }
  183. if ( !isset( $params['persistent'] ) ) {
  184. $params['persistent'] = $GLOBALS['wgMemCachedPersistent'];
  185. }
  186. if ( !isset( $params['timeout'] ) ) {
  187. $params['timeout'] = $GLOBALS['wgMemCachedTimeout'];
  188. }
  189. }
  190. return new $class( $params );
  191. } else {
  192. throw new InvalidArgumentException( "The definition of cache type \""
  193. . print_r( $params, true ) . "\" lacks both "
  194. . "factory and class parameters." );
  195. }
  196. }
  197. /**
  198. * Factory function for CACHE_ANYTHING (referenced from DefaultSettings.php)
  199. *
  200. * CACHE_ANYTHING means that stuff has to be cached, not caching is not an option.
  201. * If a caching method is configured for any of the main caches ($wgMainCacheType,
  202. * $wgMessageCacheType, $wgParserCacheType), then CACHE_ANYTHING will effectively
  203. * be an alias to the configured cache choice for that.
  204. * If no cache choice is configured (by default $wgMainCacheType is CACHE_NONE),
  205. * then CACHE_ANYTHING will forward to CACHE_DB.
  206. *
  207. * @param array $params
  208. * @return BagOStuff
  209. */
  210. public static function newAnything( $params ) {
  211. global $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType;
  212. $candidates = [ $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType ];
  213. foreach ( $candidates as $candidate ) {
  214. if ( $candidate !== CACHE_NONE && $candidate !== CACHE_ANYTHING ) {
  215. $cache = self::getInstance( $candidate );
  216. // CACHE_ACCEL might default to nothing if no APCu
  217. // See includes/ServiceWiring.php
  218. if ( !( $cache instanceof EmptyBagOStuff ) ) {
  219. return $cache;
  220. }
  221. }
  222. }
  223. if ( MediaWikiServices::getInstance()->isServiceDisabled( 'DBLoadBalancer' ) ) {
  224. // The LoadBalancer is disabled, probably because
  225. // MediaWikiServices::disableStorageBackend was called.
  226. $candidate = CACHE_NONE;
  227. } else {
  228. $candidate = CACHE_DB;
  229. }
  230. return self::getInstance( $candidate );
  231. }
  232. /**
  233. * Factory function for CACHE_ACCEL (referenced from DefaultSettings.php)
  234. *
  235. * This will look for any APC or APCu style server-local cache.
  236. * A fallback cache can be specified if none is found.
  237. *
  238. * // Direct calls
  239. * ObjectCache::getLocalServerInstance( $fallbackType );
  240. *
  241. * // From $wgObjectCaches via newFromParams()
  242. * ObjectCache::getLocalServerInstance( [ 'fallback' => $fallbackType ] );
  243. *
  244. * @param int|string|array $fallback Fallback cache or parameter map with 'fallback'
  245. * @return BagOStuff
  246. * @throws InvalidArgumentException
  247. * @since 1.27
  248. */
  249. public static function getLocalServerInstance( $fallback = CACHE_NONE ) {
  250. $cache = MediaWikiServices::getInstance()->getLocalServerObjectCache();
  251. if ( $cache instanceof EmptyBagOStuff ) {
  252. if ( is_array( $fallback ) ) {
  253. $fallback = $fallback['fallback'] ?? CACHE_NONE;
  254. }
  255. $cache = self::getInstance( $fallback );
  256. }
  257. return $cache;
  258. }
  259. /**
  260. * Create a new cache object of the specified type.
  261. *
  262. * @since 1.26
  263. * @param string $id A key in $wgWANObjectCaches.
  264. * @return WANObjectCache
  265. * @throws UnexpectedValueException
  266. */
  267. private static function newWANCacheFromId( $id ) {
  268. global $wgWANObjectCaches, $wgObjectCaches;
  269. if ( !isset( $wgWANObjectCaches[$id] ) ) {
  270. throw new UnexpectedValueException(
  271. "Cache type \"$id\" requested is not present in \$wgWANObjectCaches." );
  272. }
  273. $params = $wgWANObjectCaches[$id];
  274. if ( !isset( $wgObjectCaches[$params['cacheId']] ) ) {
  275. throw new UnexpectedValueException(
  276. "Cache type \"{$params['cacheId']}\" is not present in \$wgObjectCaches." );
  277. }
  278. $params['store'] = $wgObjectCaches[$params['cacheId']];
  279. return self::newWANCacheFromParams( $params );
  280. }
  281. /**
  282. * Create a new cache object of the specified type.
  283. *
  284. * @since 1.28
  285. * @param array $params
  286. * @return WANObjectCache
  287. * @throws UnexpectedValueException
  288. * @suppress PhanTypeMismatchReturn
  289. * @deprecated since 1.34 Use MediaWikiServices::getMainWANObjectCache
  290. * instead or use WANObjectCache::__construct directly
  291. */
  292. public static function newWANCacheFromParams( array $params ) {
  293. wfDeprecated( __METHOD__, '1.34' );
  294. global $wgCommandLineMode, $wgSecretKey;
  295. $services = MediaWikiServices::getInstance();
  296. $params['cache'] = self::newFromParams( $params['store'] );
  297. $params['logger'] = LoggerFactory::getInstance( $params['loggroup'] ?? 'objectcache' );
  298. if ( !$wgCommandLineMode ) {
  299. // Send the statsd data post-send on HTTP requests; avoid in CLI mode (T181385)
  300. $params['stats'] = $services->getStatsdDataFactory();
  301. // Let pre-emptive refreshes happen post-send on HTTP requests
  302. $params['asyncHandler'] = [ DeferredUpdates::class, 'addCallableUpdate' ];
  303. }
  304. $params['secret'] = $params['secret'] ?? $wgSecretKey;
  305. $class = $params['class'];
  306. return new $class( $params );
  307. }
  308. /**
  309. * Get the main cluster-local cache object.
  310. *
  311. * @since 1.27
  312. * @return BagOStuff
  313. */
  314. public static function getLocalClusterInstance() {
  315. global $wgMainCacheType;
  316. return self::getInstance( $wgMainCacheType );
  317. }
  318. /**
  319. * Clear all the cached instances.
  320. */
  321. public static function clear() {
  322. self::$instances = [];
  323. self::$wanInstances = [];
  324. }
  325. /**
  326. * Detects which local server cache library is present and returns a configuration for it
  327. * @since 1.32
  328. *
  329. * @return int|string Index to cache in $wgObjectCaches
  330. */
  331. public static function detectLocalServerCache() {
  332. if ( function_exists( 'apcu_fetch' ) ) {
  333. // Make sure the APCu methods actually store anything
  334. if ( PHP_SAPI !== 'cli' || ini_get( 'apc.enable_cli' ) ) {
  335. return 'apcu';
  336. }
  337. } elseif ( function_exists( 'apc_fetch' ) ) {
  338. // Make sure the APC methods actually store anything
  339. if ( PHP_SAPI !== 'cli' || ini_get( 'apc.enable_cli' ) ) {
  340. return 'apc';
  341. }
  342. } elseif ( function_exists( 'wincache_ucache_get' ) ) {
  343. return 'wincache';
  344. }
  345. return CACHE_NONE;
  346. }
  347. }