Avoid stack overflow in LoadBalancer with CACHE_DB WAN/server cache
* Add the ability to expose key BagOStuff attributes like whether the emulation SQL cache is being used. Several callers use hacks to detect this and can be updated later. * Fallback to a safe empty WAN cache in the CACHE_DB case. This fixes a regression fromf4bf52e843. * Also add this protection to the server cache, which could break similarly before too. * Also fix CACHE_ANYTHING by avoid the recursion risk fromfc1d4d7960by just checking the disabled service map. Bug: T123829 Bug: T141804 Change-Id: I17ee26138f69e01ec1aaddb55ab27caa4d542193
This commit is contained in:
parent
4817afdfda
commit
5f921702d2
10 changed files with 84 additions and 10 deletions
|
|
@ -367,4 +367,12 @@ class ServiceContainer implements DestructibleService {
|
|||
return $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return bool Whether the service is disabled
|
||||
* @since 1.28
|
||||
*/
|
||||
public function isServiceDisabled( $name ) {
|
||||
return isset( $this->disabled[$name] );
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,8 +138,20 @@ class LoadBalancer {
|
|||
}
|
||||
}
|
||||
|
||||
$this->srvCache = ObjectCache::getLocalServerInstance();
|
||||
$this->wanCache = ObjectCache::getMainWANInstance();
|
||||
// Use APC/memcached style caching, but avoids loops with CACHE_DB (T141804)
|
||||
// @TODO: inject these in via LBFactory at some point
|
||||
$cache = ObjectCache::getLocalServerInstance();
|
||||
if ( $cache->getQoS( $cache::ATTR_EMULATION ) > $cache::QOS_EMULATION_SQL ) {
|
||||
$this->srvCache = $cache;
|
||||
} else {
|
||||
$this->srvCache = new EmptyBagOStuff();
|
||||
}
|
||||
$wCache = ObjectCache::getMainWANInstance();
|
||||
if ( $wCache->getQoS( $wCache::ATTR_EMULATION ) > $wCache::QOS_EMULATION_SQL ) {
|
||||
$this->wanCache = $wCache;
|
||||
} else {
|
||||
$this->wanCache = WANObjectCache::newEmpty();
|
||||
}
|
||||
|
||||
if ( isset( $params['trxProfiler'] ) ) {
|
||||
$this->trxProfiler = $params['trxProfiler'];
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface {
|
|||
/** @var array[] Lock tracking */
|
||||
protected $locks = [];
|
||||
|
||||
/** @var integer */
|
||||
/** @var integer ERR_* class constant */
|
||||
protected $lastError = self::ERR_NONE;
|
||||
|
||||
/** @var string */
|
||||
|
|
@ -70,6 +70,9 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface {
|
|||
/** @var bool */
|
||||
private $dupeTrackScheduled = false;
|
||||
|
||||
/** @var integer[] Map of (ATTR_* class constant => QOS_* class constant) */
|
||||
protected $attrMap = [];
|
||||
|
||||
/** Possible values for getLastError() */
|
||||
const ERR_NONE = 0; // no error
|
||||
const ERR_NO_RESPONSE = 1; // no response
|
||||
|
|
@ -734,4 +737,34 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface {
|
|||
public function makeKey() {
|
||||
return $this->makeKeyInternal( $this->keyspace, func_get_args() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param integer $flag ATTR_* class constant
|
||||
* @return integer QOS_* class constant
|
||||
* @since 1.28
|
||||
*/
|
||||
public function getQoS( $flag ) {
|
||||
return isset( $this->attrMap[$flag] ) ? $this->attrMap[$flag] : self::QOS_UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the flag maps of one or more BagOStuff objects into a "lowest common denominator" map
|
||||
*
|
||||
* @param BagOStuff[] $bags
|
||||
* @return integer[] Resulting flag map (class ATTR_* constant => class QOS_* constant)
|
||||
*/
|
||||
protected function mergeFlagMaps( array $bags ) {
|
||||
$map = [];
|
||||
foreach ( $bags as $bag ) {
|
||||
foreach ( $bag->attrMap as $attr => $rank ) {
|
||||
if ( isset( $map[$attr] ) ) {
|
||||
$map[$attr] = min( $map[$attr], $rank );
|
||||
} else {
|
||||
$map[$attr] = $rank;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,8 +42,10 @@ class CachedBagOStuff extends HashBagOStuff {
|
|||
* @param array $params Parameters for HashBagOStuff
|
||||
*/
|
||||
function __construct( BagOStuff $backend, $params = [] ) {
|
||||
$this->backend = $backend;
|
||||
parent::__construct( $params );
|
||||
|
||||
$this->backend = $backend;
|
||||
$this->attrMap = $backend->attrMap;
|
||||
}
|
||||
|
||||
protected function doGet( $key, $flags = 0 ) {
|
||||
|
|
|
|||
|
|
@ -42,4 +42,11 @@ interface IExpiringStore {
|
|||
const TTL_PROC_LONG = 30; // loose cache time that can survive slow web requests
|
||||
|
||||
const TTL_INDEFINITE = 0;
|
||||
|
||||
// Attribute and QoS constants; higher QOS values with the same prefix rank higher...
|
||||
// Medium attributes constants related to emulation or media type
|
||||
const ATTR_EMULATION = 1;
|
||||
const QOS_EMULATION_SQL = 1;
|
||||
// Generic "unknown" value that is useful for comparisons (e.g. always good enough)
|
||||
const QOS_UNKNOWN = INF;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ class MultiWriteBagOStuff extends BagOStuff {
|
|||
$this->caches[] = ObjectFactory::getObjectFromSpec( $cacheInfo );
|
||||
}
|
||||
}
|
||||
$this->mergeFlagMaps( $this->caches );
|
||||
|
||||
$this->asyncWrites = (
|
||||
isset( $params['replication'] ) &&
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ class ReplicatedBagOStuff extends BagOStuff {
|
|||
$this->readStore = ( $params['readFactory'] instanceof BagOStuff )
|
||||
? $params['readFactory']
|
||||
: ObjectFactory::getObjectFromSpec( $params['readFactory'] );
|
||||
$this->attrMap = $this->mergeFlagMaps( [ $this->readStore, $this->writeStore ] );
|
||||
}
|
||||
|
||||
public function setDebug( $debug ) {
|
||||
|
|
|
|||
|
|
@ -977,7 +977,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
|
|||
|
||||
/**
|
||||
* Get the "last error" registered; clearLastError() should be called manually
|
||||
* @return int ERR_* constant for the "last error" registry
|
||||
* @return int ERR_* class constant for the "last error" registry
|
||||
*/
|
||||
final public function getLastError() {
|
||||
if ( $this->lastRelayError ) {
|
||||
|
|
@ -1019,6 +1019,15 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
|
|||
$this->procCache->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param integer $flag ATTR_* class constant
|
||||
* @return integer QOS_* class constant
|
||||
* @since 1.28
|
||||
*/
|
||||
public function getQoS( $flag ) {
|
||||
return $this->cache->getQoS( $flag );
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the actual async bus purge of a key
|
||||
*
|
||||
|
|
|
|||
|
|
@ -228,14 +228,12 @@ class ObjectCache {
|
|||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Make sure we actually have a DB backend before falling back to CACHE_DB
|
||||
MediaWikiServices::getInstance()->getDBLoadBalancer();
|
||||
$candidate = CACHE_DB;
|
||||
} catch ( ServiceDisabledException $e ) {
|
||||
if ( MediaWikiServices::getInstance()->isServiceDisabled( 'DBLoadBalancer' ) ) {
|
||||
// The LoadBalancer is disabled, probably because
|
||||
// MediaWikiServices::disableStorageBackend was called.
|
||||
$candidate = CACHE_NONE;
|
||||
} else {
|
||||
$candidate = CACHE_DB;
|
||||
}
|
||||
|
||||
return self::getInstance( $candidate );
|
||||
|
|
|
|||
|
|
@ -91,6 +91,9 @@ class SqlBagOStuff extends BagOStuff {
|
|||
*/
|
||||
public function __construct( $params ) {
|
||||
parent::__construct( $params );
|
||||
|
||||
$this->attrMap[self::ATTR_EMULATION] = self::QOS_EMULATION_SQL;
|
||||
|
||||
if ( isset( $params['servers'] ) ) {
|
||||
$this->serverInfos = [];
|
||||
$this->serverTags = [];
|
||||
|
|
|
|||
Loading…
Reference in a new issue