Fix the cache timestamp for forced updates.

Without this patch, the forcelinksupdate parameter of ApiPurge
was inoperational, caused by the fact that RefreshLinksJob got
the original revision's timestamp in the rootJobTimestamp parameter,
instead of the time at which the new ParserOutput was created.

See <https://phabricator.wikimedia.org/T210307#4771586> for details.

Bug: T210307
Change-Id: I281d6d0ed112b35e160775e528d363ce4770990a
This commit is contained in:
daniel 2018-11-24 16:59:58 +01:00
parent 6228415557
commit be2bde8705
3 changed files with 90 additions and 6 deletions

View file

@ -151,6 +151,9 @@ class DerivedPageDataUpdater implements IDBAccessObject {
*/
private $options = [
'changed' => true,
// newrev is true if prepareUpdate is handling the creation of a new revision,
// as opposed to a null edit or a forced update.
'newrev' => false,
'created' => false,
'moved' => false,
'restored' => false,
@ -1110,12 +1113,14 @@ class DerivedPageDataUpdater implements IDBAccessObject {
// Override fields defined in $this->options with values from $options.
$this->options = array_intersect_key( $options, $this->options ) + $this->options;
if ( isset( $this->pageState['oldId'] ) ) {
$oldId = $this->pageState['oldId'];
if ( $this->revision ) {
$oldId = $this->pageState['oldId'] ?? 0;
$this->options['newrev'] = ( $revision->getId() !== $oldId );
} elseif ( isset( $this->options['oldrevision'] ) ) {
/** @var Revision|RevisionRecord $oldRev */
$oldRev = $this->options['oldrevision'];
$oldId = $oldRev->getId();
$this->options['newrev'] = ( $revision->getId() !== $oldId );
} else {
$oldId = $revision->getParentId();
}
@ -1611,8 +1616,8 @@ class DerivedPageDataUpdater implements IDBAccessObject {
// Save it to the parser cache. Use the revision timestamp in the case of a
// freshly saved edit, as that matches page_touched and a mismatch would trigger an
// unnecessary reparse.
$timestamp = $this->options['changed'] ? $this->revision->getTimestamp()
: $output->getTimestamp();
$timestamp = $this->options['newrev'] ? $this->revision->getTimestamp()
: $output->getCacheTime();
$this->parserCache->save(
$output, $wikiPage, $this->getCanonicalParserOptions(),
$timestamp, $this->revision->getId()

View file

@ -656,7 +656,7 @@ class DerivedPageDataUpdaterTest extends MediaWikiTestCase {
RevisionSlotsUpdate $update,
User $user,
$comment,
$id,
$id = 0,
$parentId = 0
) {
$rev = new MutableRevisionRecord( $title );
@ -664,10 +664,13 @@ class DerivedPageDataUpdaterTest extends MediaWikiTestCase {
$rev->applyUpdate( $update );
$rev->setUser( $user );
$rev->setComment( CommentStoreComment::newUnsavedComment( $comment ) );
$rev->setId( $id );
$rev->setPageId( $title->getArticleID() );
$rev->setParentId( $parentId );
if ( $id ) {
$rev->setId( $id );
}
return $rev;
}
@ -942,6 +945,79 @@ class DerivedPageDataUpdaterTest extends MediaWikiTestCase {
// TODO: test category membership update (with setRcWatchCategoryMembership())
}
/**
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::doParserCacheUpdate()
*/
public function testDoParserCacheUpdate() {
if ( $this->hasMultiSlotSupport() ) {
MediaWikiServices::getInstance()->getSlotRoleRegistry()->defineRoleWithModel(
'aux',
CONTENT_MODEL_WIKITEXT
);
}
$page = $this->getPage( __METHOD__ );
$this->createRevision( $page, 'Dummy' );
$user = $this->getTestUser()->getUser();
$update = new RevisionSlotsUpdate();
$update->modifyContent( 'main', new WikitextContent( 'first [[Main]]' ) );
if ( $this->hasMultiSlotSupport() ) {
$update->modifyContent( 'aux', new WikitextContent( 'Aux [[Nix]]' ) );
}
// Emulate update after edit ----------
$pcache = MediaWikiServices::getInstance()->getParserCache();
$pcache->deleteOptionsKey( $page );
$rev = $this->makeRevision( $page->getTitle(), $update, $user, 'rev', null );
$rev->setTimestamp( '20100101000000' );
$rev->setParentId( $page->getLatest() );
$updater = $this->getDerivedPageDataUpdater( $page );
$updater->prepareContent( $user, $update, false );
$rev->setId( 11 );
$updater->prepareUpdate( $rev );
// Force the page timestamp, so we notice whether ParserOutput::getTimestamp
// or ParserOutput::getCacheTime are used.
$page->setTimestamp( $rev->getTimestamp() );
$updater->doParserCacheUpdate();
// The cached ParserOutput should not use the revision timestamp
$cached = $pcache->get( $page, $updater->getCanonicalParserOptions(), true );
$this->assertInternalType( 'object', $cached );
$this->assertSame( $updater->getCanonicalParserOutput(), $cached );
$this->assertSame( $rev->getTimestamp(), $cached->getCacheTime() );
$this->assertSame( $rev->getId(), $cached->getCacheRevisionId() );
// Emulate forced update of an old revision ----------
$pcache->deleteOptionsKey( $page );
$updater = $this->getDerivedPageDataUpdater( $page );
$updater->prepareUpdate( $rev );
// Force the page timestamp, so we notice whether ParserOutput::getTimestamp
// or ParserOutput::getCacheTime are used.
$page->setTimestamp( $rev->getTimestamp() );
$updater->doParserCacheUpdate();
// The cached ParserOutput should not use the revision timestamp
$cached = $pcache->get( $page, $updater->getCanonicalParserOptions(), true );
$this->assertInternalType( 'object', $cached );
$this->assertSame( $updater->getCanonicalParserOutput(), $cached );
$this->assertGreaterThan( $rev->getTimestamp(), $cached->getCacheTime() );
$this->assertSame( $rev->getId(), $cached->getCacheRevisionId() );
}
/**
* @return bool
*/
private function hasMultiSlotSupport() {
global $wgMultiContentRevisionSchemaMigrationStage;

View file

@ -29,6 +29,9 @@ class PageUpdaterTest extends MediaWikiTestCase {
'aux',
CONTENT_MODEL_WIKITEXT
);
$this->tablesUsed[] = 'logging';
$this->tablesUsed[] = 'recentchanges';
}
private function getDummyTitle( $method ) {