Merge "LinkCache: Try invalidating cache before throwing"

This commit is contained in:
jenkins-bot 2021-11-02 14:51:14 +00:00 committed by Gerrit Code Review
commit 3d965e3a8b
2 changed files with 89 additions and 22 deletions

View file

@ -425,49 +425,46 @@ class LinkCache implements LoggerAwareInterface {
} }
/** /**
* Returns the row for the page if the page exists (subject to race conditions). * @param TitleValue|null $link
* The row will be returned from local cache or WAN cache if possible, or it * @param callable|null $fetchCallback
* will be looked up using the callback provided. * @param int $queryFlags
* * @return array [ $shouldAddGoodLink, $row ], $shouldAddGoodLink is a bool indicating
* @param int $ns * whether addGoodLinkObjFromRow should be called, and $row is the row the caller was looking
* @param string $dbkey * for (or false, when it was not found).
* @param callable|null $fetchCallback A callback that will retrieve the link row with the
* signature ( IDatabase $db, int $ns, string $dbkey, array $queryOptions ): ?stdObj.
* @param int $queryFlags IDBAccessObject::READ_XXX
*
* @return stdClass|null
* @internal for use by PageStore. Other code should use a PageLookup instead.
*/ */
public function getGoodLinkRow( private function getGoodLinkRowInternal(
int $ns, ?TitleValue $link,
string $dbkey,
callable $fetchCallback = null, callable $fetchCallback = null,
int $queryFlags = IDBAccessObject::READ_NORMAL int $queryFlags = IDBAccessObject::READ_NORMAL
): ?stdClass { ): array {
$link = TitleValue::tryNew( $ns, $dbkey );
$key = $link ? $this->getCacheKey( $link ) : null; $key = $link ? $this->getCacheKey( $link ) : null;
if ( $key === null ) { if ( $key === null ) {
return null; return [ false, false ];
} }
$ns = $link->getNamespace();
$dbkey = $link->getDBkey();
$callerShouldAddGoodLink = false;
if ( $this->mForUpdate ) { if ( $this->mForUpdate ) {
$queryFlags |= IDBAccessObject::READ_LATEST; $queryFlags |= IDBAccessObject::READ_LATEST;
} }
$forUpdate = $queryFlags & IDBAccessObject::READ_LATEST; $forUpdate = $queryFlags & IDBAccessObject::READ_LATEST;
if ( !$forUpdate && $this->isBadLink( $key ) ) { if ( !$forUpdate && $this->isBadLink( $key ) ) {
return null; return [ $callerShouldAddGoodLink, false ];
} }
[ $row, $rowFlags ] = $this->goodLinks->get( $key ); [ $row, $rowFlags ] = $this->goodLinks->get( $key );
if ( $row && $rowFlags >= $queryFlags ) { if ( $row && $rowFlags >= $queryFlags ) {
return $row; return [ $callerShouldAddGoodLink, $row ];
} }
if ( !$fetchCallback ) { if ( !$fetchCallback ) {
return null; return [ $callerShouldAddGoodLink, false ];
} }
$callerShouldAddGoodLink = true;
if ( $this->usePersistentCache( $ns ) && !$forUpdate ) { if ( $this->usePersistentCache( $ns ) && !$forUpdate ) {
// Some pages are often transcluded heavily, so use persistent caching // Some pages are often transcluded heavily, so use persistent caching
$wanCacheKey = $this->wanCache->makeKey( 'page', $ns, sha1( $dbkey ) ); $wanCacheKey = $this->wanCache->makeKey( 'page', $ns, sha1( $dbkey ) );
@ -493,8 +490,54 @@ class LinkCache implements LoggerAwareInterface {
$row = $fetchCallback( $dbr, $ns, $dbkey, $options ); $row = $fetchCallback( $dbr, $ns, $dbkey, $options );
} }
return [ $callerShouldAddGoodLink, $row ];
}
/**
* Returns the row for the page if the page exists (subject to race conditions).
* The row will be returned from local cache or WAN cache if possible, or it
* will be looked up using the callback provided.
*
* @param int $ns
* @param string $dbkey
* @param callable|null $fetchCallback A callback that will retrieve the link row with the
* signature ( IDatabase $db, int $ns, string $dbkey, array $queryOptions ): ?stdObj.
* @param int $queryFlags IDBAccessObject::READ_XXX
*
* @return stdClass|null
* @internal for use by PageStore. Other code should use a PageLookup instead.
*/
public function getGoodLinkRow(
int $ns,
string $dbkey,
callable $fetchCallback = null,
int $queryFlags = IDBAccessObject::READ_NORMAL
): ?stdClass {
$link = TitleValue::tryNew( $ns, $dbkey );
if ( $link === null ) {
return null;
}
[ $shouldAddGoodLink, $row ] = $this->getGoodLinkRowInternal(
$link,
$fetchCallback,
$queryFlags
);
if ( $row ) { if ( $row ) {
$this->addGoodLinkObjFromRow( $link, $row ); if ( $shouldAddGoodLink ) {
try {
$this->addGoodLinkObjFromRow( $link, $row, $queryFlags );
} catch ( InvalidArgumentException $e ) {
// a field is missing from $row; maybe we used a cache?; invalidate it and try again
$this->invalidateTitle( $link );
[ $shouldAddGoodLink, $row ] = $this->getGoodLinkRowInternal(
$link,
$fetchCallback,
$queryFlags
);
$this->addGoodLinkObjFromRow( $link, $row, $queryFlags );
}
}
} else { } else {
$this->addBadLinkObj( $link ); $this->addBadLinkObj( $link );
} }

View file

@ -492,6 +492,30 @@ class LinkCacheTest extends MediaWikiIntegrationTestCase {
); );
} }
/**
* @covers LinkCache::getGoodLinkRow()
*/
public function testGetGoodLinkRowGetsIncompleteCachedInfo() {
// Pages in some namespaces use the WAN cache: Template, File, Category, MediaWiki
$existing = new TitleValue( NS_TEMPLATE, 'Existing' );
$brokenRow = $this->getPageRow( 3 );
unset( $brokenRow->page_len ); // make incomplete row
$cache = new HashBagOStuff();
$wanCache = new WANObjectCache( [ 'cache' => $cache ] );
$linkCache = $this->newLinkCache( $wanCache );
// force the incomplete row into the cache
$keys = $linkCache->getMutableCacheKeys( $wanCache, $existing );
$wanCache->set( $keys[0], $brokenRow );
// check that we are not getting the broken row, but load a good row
$callback = [ $this, 'getRowIfExisting' ];
$row = $linkCache->getGoodLinkRow( $existing->getNamespace(), $existing->getDBkey(), $callback );
$this->assertNotEquals( $brokenRow, $row );
}
/** /**
* @covers LinkCache::getGoodLinkRow() * @covers LinkCache::getGoodLinkRow()
* @covers LinkCache::getMutableCacheKeys() * @covers LinkCache::getMutableCacheKeys()