HtmlOutputRendererHelper: allow parser cache to be disabled.
This is needed so we can ramp up parser cache writes in a controlled manner. Bug: T322672 Change-Id: I7d97c9e2d4009029dc64f9c0a369f68098185520
This commit is contained in:
parent
2839a0b9d3
commit
c6a0d433ec
4 changed files with 163 additions and 20 deletions
|
|
@ -107,6 +107,14 @@ class HtmlOutputRendererHelper {
|
|||
/** @var string|null */
|
||||
private $targetLanguageCode;
|
||||
|
||||
/**
|
||||
* Flags to be passed as $options to ParsoidOutputAccess::getParserOutput,
|
||||
* to control parser cache access.
|
||||
*
|
||||
* @var int Use ParsoidOutputAccess::OPT_*
|
||||
*/
|
||||
private $parsoidOutputAccessOptions = 0;
|
||||
|
||||
/**
|
||||
* @param ParsoidOutputStash $parsoidOutputStash
|
||||
* @param StatsdDataFactoryInterface $statsDataFactory
|
||||
|
|
@ -141,6 +149,18 @@ class HtmlOutputRendererHelper {
|
|||
$this->flavor = $flavor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls how the parser cache is used.
|
||||
*
|
||||
* @param bool $read Whether we should look for cached output before parsing
|
||||
* @param bool $write Whether we should cache output after parsing
|
||||
*/
|
||||
public function setUseParserCache( bool $read, bool $write ) {
|
||||
$this->parsoidOutputAccessOptions =
|
||||
( $read ? 0 : ParsoidOutputAccess::OPT_FORCE_PARSE ) |
|
||||
( $write ? 0 : ParsoidOutputAccess::OPT_NO_UPDATE_CACHE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether stashing should be applied.
|
||||
*
|
||||
|
|
@ -386,7 +406,8 @@ class HtmlOutputRendererHelper {
|
|||
$status = $this->parsoidOutputAccess->getParserOutput(
|
||||
$this->page,
|
||||
$parserOptions,
|
||||
$this->revisionOrId
|
||||
$this->revisionOrId,
|
||||
$this->parsoidOutputAccessOptions
|
||||
);
|
||||
} else {
|
||||
$status = $this->parsoidOutputAccess->parse(
|
||||
|
|
|
|||
|
|
@ -69,6 +69,11 @@ class ParsoidOutputAccess {
|
|||
/** @var int Do not check the cache before parsing (force parse) */
|
||||
public const OPT_FORCE_PARSE = 1;
|
||||
|
||||
/**
|
||||
* @var int Do not update the cache after parsing.
|
||||
*/
|
||||
public const OPT_NO_UPDATE_CACHE = 2;
|
||||
|
||||
public const CONSTRUCTOR_OPTIONS = [
|
||||
MainConfigNames::ParsoidCacheConfig,
|
||||
MainConfigNames::ParsoidSettings,
|
||||
|
|
@ -206,10 +211,14 @@ class ParsoidOutputAccess {
|
|||
}
|
||||
|
||||
$startTime = microtime( true );
|
||||
$status = $this->parse( $page, $parserOpts, [ 'pageBundle' => true ], $revision );
|
||||
$status = $this->parse( $page, $parserOpts, [], $revision );
|
||||
$time = microtime( true ) - $startTime;
|
||||
|
||||
if ( $status->isOK() ) {
|
||||
if ( !$status->isOK() ) {
|
||||
$this->stats->increment( $statsKey . '.save.notok' );
|
||||
} elseif ( $options & self::OPT_NO_UPDATE_CACHE ) {
|
||||
$this->stats->increment( $statsKey . '.save.disabled' );
|
||||
} else {
|
||||
if ( $time > $this->parsoidCacheConfig->get( 'CacheThresholdTime' ) ) {
|
||||
$parserOutput = $status->getValue();
|
||||
$now = $parserOutput->getCacheTime();
|
||||
|
|
@ -223,8 +232,6 @@ class ParsoidOutputAccess {
|
|||
} else {
|
||||
$this->stats->increment( $statsKey . '.save.skipfast' );
|
||||
}
|
||||
} else {
|
||||
$this->stats->increment( $statsKey . '.save.notok' );
|
||||
}
|
||||
|
||||
return $status;
|
||||
|
|
|
|||
|
|
@ -16,9 +16,11 @@ use MediaWiki\Page\PageIdentity;
|
|||
use MediaWiki\Page\PageIdentityValue;
|
||||
use MediaWiki\Page\PageLookup;
|
||||
use MediaWiki\Page\PageRecord;
|
||||
use MediaWiki\Parser\ParserCacheFactory;
|
||||
use MediaWiki\Parser\Parsoid\PageBundleParserOutputConverter;
|
||||
use MediaWiki\Parser\Parsoid\ParsoidOutputAccess;
|
||||
use MediaWiki\Parser\Parsoid\ParsoidRenderID;
|
||||
use MediaWiki\Parser\RevisionOutputCache;
|
||||
use MediaWiki\Rest\Handler\HtmlOutputRendererHelper;
|
||||
use MediaWiki\Rest\LocalizedHttpException;
|
||||
use MediaWiki\Rest\ResponseInterface;
|
||||
|
|
@ -28,6 +30,7 @@ use MediaWiki\Revision\SlotRecord;
|
|||
use MediaWikiIntegrationTestCase;
|
||||
use MWTimestamp;
|
||||
use NullStatsdDataFactory;
|
||||
use ParserCache;
|
||||
use ParserOptions;
|
||||
use ParserOutput;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
|
|
@ -36,6 +39,7 @@ use Status;
|
|||
use User;
|
||||
use Wikimedia\Message\MessageValue;
|
||||
use Wikimedia\Parsoid\Core\ClientError;
|
||||
use Wikimedia\Parsoid\Core\PageBundle;
|
||||
use Wikimedia\Parsoid\Core\ResourceLimitExceededException;
|
||||
use Wikimedia\Parsoid\Parsoid;
|
||||
use Wikimedia\TestingAccessWrapper;
|
||||
|
|
@ -544,6 +548,57 @@ class HtmlOutputRendererHelperTest extends MediaWikiIntegrationTestCase {
|
|||
];
|
||||
}
|
||||
|
||||
private function newRealParsoidOutputAccess( $overrides = [] ) {
|
||||
if ( isset( $overrides['parsoid'] ) ) {
|
||||
$parsoid = $overrides['parsoid'];
|
||||
} else {
|
||||
$parsoid = $this->createNoOpMock( Parsoid::class, [ 'wikitext2html' ] );
|
||||
$parsoid->method( 'wikitext2html' )
|
||||
->willReturn( new PageBundle( 'This is HTML' ) );
|
||||
}
|
||||
|
||||
if ( isset( $overrides['parserCache'] ) ) {
|
||||
$parserCache = $overrides['parserCache'];
|
||||
} else {
|
||||
$parserCache = $this->createNoOpMock( ParserCache::class, [ 'get', 'save' ] );
|
||||
$parserCache->method( 'get' )->willReturn( false );
|
||||
$parserCache->method( 'save' )->willReturn( null );
|
||||
}
|
||||
|
||||
if ( isset( $overrides['revisionCache'] ) ) {
|
||||
$revisionCache = $overrides['revisionCache'];
|
||||
} else {
|
||||
$revisionCache = $this->createNoOpMock( RevisionOutputCache::class, [ 'get', 'save' ] );
|
||||
$revisionCache->method( 'get' )->willReturn( false );
|
||||
$revisionCache->method( 'save' )->willReturn( null );
|
||||
}
|
||||
|
||||
$parserCacheFactory = $this->createNoOpMock(
|
||||
ParserCacheFactory::class,
|
||||
[ 'getParserCache', 'getRevisionOutputCache' ]
|
||||
);
|
||||
$parserCacheFactory->method( 'getParserCache' )->willReturn( $parserCache );
|
||||
$parserCacheFactory->method( 'getRevisionOutputCache' )->willReturn( $revisionCache );
|
||||
|
||||
$services = $this->getServiceContainer();
|
||||
|
||||
return new ParsoidOutputAccess(
|
||||
new ServiceOptions(
|
||||
ParsoidOutputAccess::CONSTRUCTOR_OPTIONS,
|
||||
$services->getMainConfig(),
|
||||
[ 'ParsoidWikiID' => 'MyWiki' ]
|
||||
),
|
||||
$parserCacheFactory,
|
||||
$this->createNoOpMock( PageLookup::class ),
|
||||
$services->getRevisionLookup(),
|
||||
$services->getGlobalIdGenerator(),
|
||||
new NullStatsdDataFactory(),
|
||||
$parsoid,
|
||||
$services->getParsoidSiteConfig(),
|
||||
$services->getParsoidPageConfigFactory()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideHandlesParsoidError
|
||||
*/
|
||||
|
|
@ -558,21 +613,9 @@ class HtmlOutputRendererHelperTest extends MediaWikiIntegrationTestCase {
|
|||
->willThrowException( $parsoidException );
|
||||
|
||||
/** @var ParsoidOutputAccess|MockObject $access */
|
||||
$access = new ParsoidOutputAccess(
|
||||
new ServiceOptions(
|
||||
ParsoidOutputAccess::CONSTRUCTOR_OPTIONS,
|
||||
$this->getServiceContainer()->getMainConfig(),
|
||||
[ 'ParsoidWikiID' => 'MyWiki' ]
|
||||
),
|
||||
$this->getServiceContainer()->getParserCacheFactory(),
|
||||
$this->createNoOpMock( PageLookup::class ),
|
||||
$this->getServiceContainer()->getRevisionLookup(),
|
||||
$this->getServiceContainer()->getGlobalIdGenerator(),
|
||||
new NullStatsdDataFactory(),
|
||||
$parsoid,
|
||||
$this->getServiceContainer()->getParsoidSiteConfig(),
|
||||
$this->getServiceContainer()->getParsoidPageConfigFactory()
|
||||
);
|
||||
$access = $this->newRealParsoidOutputAccess( [
|
||||
'parsoid' => $parsoid
|
||||
] );
|
||||
|
||||
$helper = $this->newHelper( null, $access );
|
||||
$helper->init( $page, self::PARAM_DEFAULTS, $this->newUser() );
|
||||
|
|
@ -581,6 +624,51 @@ class HtmlOutputRendererHelperTest extends MediaWikiIntegrationTestCase {
|
|||
$helper->getHtml();
|
||||
}
|
||||
|
||||
public function testDisableParserCacheWrite() {
|
||||
$page = $this->getExistingTestPage( __METHOD__ );
|
||||
|
||||
// NOTE: The save() method is not supported and will throw!
|
||||
// The point of this test case is asserting that save() isn't called.
|
||||
$parserCache = $this->createNoOpMock( ParserCache::class, [ 'get' ] );
|
||||
$parserCache->method( 'get' )->willReturn( false );
|
||||
|
||||
/** @var ParsoidOutputAccess|MockObject $access */
|
||||
$access = $this->newRealParsoidOutputAccess( [
|
||||
'parserCache' => $parserCache,
|
||||
'revisionCache' => $this->createNoOpMock( RevisionOutputCache::class ),
|
||||
] );
|
||||
|
||||
$helper = $this->newHelper( null, $access );
|
||||
$helper->init( $page, self::PARAM_DEFAULTS, $this->newUser() );
|
||||
|
||||
// Set read = true, write = false
|
||||
$helper->setUseParserCache( true, false );
|
||||
$helper->getHtml();
|
||||
}
|
||||
|
||||
public function testDisableParserCacheRead() {
|
||||
$page = $this->getExistingTestPage( __METHOD__ );
|
||||
|
||||
// NOTE: The get() method is not supported and will throw!
|
||||
// The point of this test case is asserting that get() isn't called.
|
||||
// We also check that save() is still called.
|
||||
$parserCache = $this->createNoOpMock( ParserCache::class, [ 'save' ] );
|
||||
$parserCache->expects( $this->once() )->method( 'save' );
|
||||
|
||||
/** @var ParsoidOutputAccess|MockObject $access */
|
||||
$access = $this->newRealParsoidOutputAccess( [
|
||||
'parserCache' => $parserCache,
|
||||
'revisionCache' => $this->createNoOpMock( RevisionOutputCache::class ),
|
||||
] );
|
||||
|
||||
$helper = $this->newHelper( null, $access );
|
||||
$helper->init( $page, self::PARAM_DEFAULTS, $this->newUser() );
|
||||
|
||||
// Set read = false, write = true
|
||||
$helper->setUseParserCache( false, true );
|
||||
$helper->getHtml();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock the language class based on a language code.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -254,6 +254,33 @@ class ParsoidOutputAccessTest extends MediaWikiIntegrationTestCase {
|
|||
$this->assertContainsHtml( self::MOCKED_HTML . ' of ' . self::WIKITEXT, $status );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that getParserOutput() will force a parse since we know that
|
||||
* the revision is not in the cache.
|
||||
*
|
||||
* @covers \MediaWiki\Parser\Parsoid\ParsoidOutputAccess::getParserOutput
|
||||
*/
|
||||
public function testLatestRevisionWithNoUpdateCache() {
|
||||
$access = $this->getParsoidOutputAccessWithCache( 2 );
|
||||
$parserOptions = $this->getParserOptions();
|
||||
|
||||
$page = $this->getNonexistingTestPage( __METHOD__ );
|
||||
$this->editPage( $page, self::WIKITEXT );
|
||||
|
||||
$status = $access->getParserOutput(
|
||||
$page,
|
||||
$parserOptions,
|
||||
null,
|
||||
ParsoidOutputAccess::OPT_NO_UPDATE_CACHE
|
||||
);
|
||||
$this->assertContainsHtml( self::MOCKED_HTML . ' of ' . self::WIKITEXT, $status );
|
||||
|
||||
// Get the ParserOutput again, this should trigger a new parse
|
||||
// since we suppressed caching above.
|
||||
$status = $access->getParserOutput( $page, $parserOptions );
|
||||
$this->assertContainsHtml( self::MOCKED_HTML . ' of ' . self::WIKITEXT, $status );
|
||||
}
|
||||
|
||||
public function provideCacheThresholdData() {
|
||||
return [
|
||||
yield "fast parse" => [ 1, 2 ], // high threshold, no caching
|
||||
|
|
|
|||
Loading…
Reference in a new issue