use MWException;
use WANObjectCache;
use Wikimedia\Assert\Assert;
-use Wikimedia\Rdbms\Database;
use Wikimedia\Rdbms\IDatabase;
-use Wikimedia\Rdbms\LoadBalancer;
+use Wikimedia\Rdbms\ILoadBalancer;
/**
* Service for storing and loading Content objects.
const TEXT_CACHE_GROUP = 'revisiontext:10';
/**
- * @var LoadBalancer
+ * @var ILoadBalancer
*/
private $dbLoadBalancer;
private $useExternalStore = false;
/**
- * @param LoadBalancer $dbLoadBalancer A load balancer for acquiring database connections
- * @param WANObjectCache $cache A cache manager for caching blobs
+ * @param ILoadBalancer $dbLoadBalancer A load balancer for acquiring database connections
+ * @param WANObjectCache $cache A cache manager for caching blobs. This can be the local
+ * wiki's default instance even if $wikiId refers to a different wiki, since
+ * makeGlobalKey() is used to constructed a key that allows cached blobs from the
+ * same database to be re-used between wikis. For example, enwiki and frwiki will
+ * use the same cache keys for blobs from the wikidatawiki database, regardless of
+ * the cache's default key space.
* @param bool|string $wikiId The ID of the target wiki database. Use false for the local wiki.
*/
public function __construct(
- LoadBalancer $dbLoadBalancer,
+ ILoadBalancer $dbLoadBalancer,
WANObjectCache $cache,
$wikiId = false
) {
}
/**
- * @return LoadBalancer
+ * @return ILoadBalancer
*/
private function getDBLoadBalancer() {
return $this->dbLoadBalancer;
// No negative caching; negative hits on text rows may be due to corrupted replica DBs
$blob = $this->cache->getWithSetCallback(
- // TODO: change key, since this is not necessarily revision text!
- $this->cache->makeKey( 'revisiontext', 'textid', $blobAddress ),
+ $this->getCacheKey( $blobAddress ),
$this->getCacheTTL(),
function ( $unused, &$ttl, &$setOpts ) use ( $blobAddress, $queryFlags ) {
- list( $index ) = DBAccessObjectUtils::getDBOptions( $queryFlags );
- $setOpts += Database::getCacheSetOptions( $this->getDBConnection( $index ) );
-
+ // Ignore $setOpts; blobs are immutable and negatives are not cached
return $this->fetchBlob( $blobAddress, $queryFlags );
},
[ 'pcGroup' => self::TEXT_CACHE_GROUP, 'pcTTL' => IExpiringStore::TTL_PROC_LONG ]
$blob = $this->expandBlob( $row->old_text, $row->old_flags, $blobAddress );
if ( $blob === false ) {
- wfWarn( __METHOD__ . ": Bad data in text row $textId." );
+ wfLogWarning( __METHOD__ . ": Bad data in text row $textId." );
return false;
}
return $blob;
}
+ /**
+ * Get a cache key for a given Blob address.
+ *
+ * The cache key is constructed in a way that allows cached blobs from the same database
+ * to be re-used between wikis. For example, enwiki and frwiki will use the same cache keys
+ * for blobs from the wikidatawiki database.
+ *
+ * @param string $blobAddress
+ * @return string
+ */
+ private function getCacheKey( $blobAddress ) {
+ return $this->cache->makeGlobalKey(
+ 'BlobStore',
+ 'address',
+ $this->dbLoadBalancer->resolveDomainID( $this->wikiId ),
+ $blobAddress
+ );
+ }
+
/**
* Expand a raw data blob according to the flags given.
*
* @param string|string[] $flags Blob flags, such as 'external' or 'gzip'.
* Note that not including 'utf-8' in $flags will cause the data to be decoded
* according to the legacy encoding specified via setLegacyEncoding.
- * @param string|null $cacheKey May be used for caching if given
+ * @param string|null $cacheKey A blob address for use in the cache key. If not given,
+ * caching is disabled.
*
* @return false|string The expanded blob or false on failure
*/
return false;
}
- if ( $cacheKey && $this->wikiId === false ) {
- // Make use of the wiki-local revision text cache.
+ if ( $cacheKey ) {
// The cached value should be decompressed, so handle that and return here.
- // NOTE: we rely on $this->cache being the right cache for $this->wikiId!
return $this->cache->getWithSetCallback(
- // TODO: change key, since this is not necessarily revision text!
- $this->cache->makeKey( 'revisiontext', 'textid', $cacheKey ),
+ $this->getCacheKey( $cacheKey ),
$this->getCacheTTL(),
function () use ( $url, $flags ) {
- // No negative caching per BlobStore::getBlob()
+ // Ignore $setOpts; blobs are immutable and negatives are not cached
$blob = ExternalStore::fetchFromURL( $url, [ 'wiki' => $this->wikiId ] );
- return $this->decompressData( $blob, $flags );
+ return $blob === false ? false : $this->decompressData( $blob, $flags );
},
[ 'pcGroup' => self::TEXT_CACHE_GROUP, 'pcTTL' => WANObjectCache::TTL_PROC_LONG ]
);
} else {
$blob = ExternalStore::fetchFromURL( $url, [ 'wiki' => $this->wikiId ] );
- return $this->decompressData( $blob, $flags );
+ return $blob === false ? false : $this->decompressData( $blob, $flags );
}
} else {
return $this->decompressData( $raw, $flags );
* @note direct use is deprecated, use getBlob() or SlotRecord::getContent() instead.
* @todo make this private, there should be no need to use this method outside this class.
*
- * @param mixed $blob Reference to a text
+ * @param string $blob Blob in compressed/encoded form.
* @param array $blobFlags Compression flags, such as 'gzip'.
* Note that not including 'utf-8' in $blobFlags will cause the data to be decoded
* according to the legacy encoding specified via setLegacyEncoding.
* @return string|bool Decompressed text, or false on failure
*/
public function decompressData( $blob, array $blobFlags ) {
- if ( $blob === false ) {
- // Text failed to be fetched; nothing to do
- return false;
- }
+ // Revision::decompressRevisionText accepted false here, so defend against that
+ Assert::parameterType( 'string', $blob, '$blob' );
if ( in_array( 'error', $blobFlags ) ) {
// Error row, return false
$blob = gzinflate( $blob );
if ( $blob === false ) {
- wfLogWarning( __METHOD__ . ': gzinflate() failed' );
+ wfWarn( __METHOD__ . ': gzinflate() failed' );
return false;
}
}
/**
* Splits a blob address into three parts: the schema, the ID, and parameters/flags.
*
+ * @since 1.33
+ *
* @param string $address
*
* @throws InvalidArgumentException
* @return array [ $schema, $id, $parameters ], with $parameters being an assoc array.
*/
- private static function splitBlobAddress( $address ) {
+ public static function splitBlobAddress( $address ) {
if ( !preg_match( '/^(\w+):(\w+)(\?(.*))?$/', $address, $m ) ) {
throw new InvalidArgumentException( "Bad blob address: $address" );
}