57 public const OPT_NO_CHECK_CACHE = 1;
60 public const OPT_FORCE_PARSE = self::OPT_NO_CHECK_CACHE;
65 public const OPT_NO_UPDATE_CACHE = 2;
72 public const OPT_NO_AUDIENCE_CHECK = 4;
78 public const OPT_NO_CACHE = self::OPT_NO_UPDATE_CACHE | self::OPT_NO_CHECK_CACHE;
84 public const OPT_LINKS_UPDATE = 8;
90 private const CACHE_PRIMARY =
'primary';
93 private const CACHE_SECONDARY =
'secondary';
102 private $localCache = [];
105 private $revisionLookup;
108 private $revisionRenderer;
111 private $statsDataFactory;
121 private $wikiPageFactory;
124 private $titleFormatter;
144 LoggerSpi $loggerSpi,
148 $this->parserCacheFactory = $parserCacheFactory;
149 $this->revisionLookup = $revisionLookup;
150 $this->revisionRenderer = $revisionRenderer;
151 $this->statsDataFactory = $statsDataFactory;
152 $this->lbFactory = $lbFactory;
153 $this->chronologyProtector = $chronologyProtector;
154 $this->loggerSpi = $loggerSpi;
155 $this->wikiPageFactory = $wikiPageFactory;
156 $this->titleFormatter = $titleFormatter;
167 private function shouldUseCache(
171 if ( $rev && !$rev->
getId() ) {
180 $wikiPage = $this->wikiPageFactory->newFromTitle( $page );
181 if ( !$page->
exists() || !$wikiPage->getContentHandler()->isParserCacheSupported() ) {
187 return self::CACHE_PRIMARY;
190 if ( !$rev->
audienceCan( RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_PUBLIC ) ) {
195 return self::CACHE_SECONDARY;
214 $isOld = $revision && $revision->getId() !== $page->getLatest();
215 $useCache = $this->shouldUseCache( $page, $revision );
216 $primaryCache = $this->getPrimaryCache( $parserOptions );
217 $classCacheKey = $primaryCache->makeParserOutputKey( $page, $parserOptions );
219 if ( $useCache === self::CACHE_PRIMARY ) {
220 if ( isset( $this->localCache[$classCacheKey][$page->
getLatest()] ) && !$isOld ) {
221 return $this->localCache[$classCacheKey][$page->
getLatest()];
223 $output = $primaryCache->get( $page, $parserOptions );
224 } elseif ( $useCache === self::CACHE_SECONDARY && $revision ) {
225 $secondaryCache = $this->getSecondaryCache( $parserOptions );
226 $output = $secondaryCache->get( $revision, $parserOptions );
231 if ( $output && !$isOld ) {
232 $this->localCache[$classCacheKey] = [ $page->
getLatest() => $output ];
236 $this->statsDataFactory->increment(
"ParserOutputAccess.Cache.$useCache.hit" );
238 $this->statsDataFactory->increment(
"ParserOutputAccess.Cache.$useCache.miss" );
241 return $output ?:
null;
272 $error = $this->checkPreconditions( $page, $revision, $options );
274 $this->statsDataFactory->increment(
"ParserOutputAccess.Case.error" );
278 $isOld = $revision && $revision->getId() !== $page->
getLatest();
280 $this->statsDataFactory->increment(
'ParserOutputAccess.Case.old' );
282 $this->statsDataFactory->increment(
'ParserOutputAccess.Case.current' );
285 if ( !( $options & self::OPT_NO_CHECK_CACHE ) ) {
286 $output = $this->getCachedParserOutput( $page, $parserOptions, $revision );
288 return Status::newGood( $output );
294 $revision = $revId ? $this->revisionLookup->getRevisionById( $revId ) :
null;
297 $this->statsDataFactory->increment(
"ParserOutputAccess.Status.norev" );
298 return Status::newFatal(
'missing-revision', $revId );
302 $work = $this->newPoolWorkArticleView( $page, $parserOptions, $revision, $options );
304 $status = $work->execute();
305 $output = $status->getValue();
306 Assert::postcondition( $output || !$status->isOK(),
'Worker returned invalid status' );
308 if ( $output && !$isOld ) {
309 $primaryCache = $this->getPrimaryCache( $parserOptions );
310 $classCacheKey = $primaryCache->makeParserOutputKey( $page, $parserOptions );
311 $this->localCache[$classCacheKey] = [ $page->
getLatest() => $output ];
314 if ( $status->isGood() ) {
315 $this->statsDataFactory->increment(
'ParserOutputAccess.Status.good' );
316 } elseif ( $status->isOK() ) {
317 $this->statsDataFactory->increment(
'ParserOutputAccess.Status.ok' );
319 $this->statsDataFactory->increment(
'ParserOutputAccess.Status.error' );
332 private function checkPreconditions(
334 ?RevisionRecord $revision =
null,
337 if ( !$page->exists() ) {
338 return Status::newFatal(
'nopagetext' );
341 if ( !( $options & self::OPT_NO_UPDATE_CACHE ) && $revision && !$revision->getId() ) {
342 throw new InvalidArgumentException(
343 'The revision does not have a known ID. Use OPT_NO_CACHE.'
347 if ( $revision && $revision->getPageId() !== $page->
getId() ) {
348 throw new InvalidArgumentException(
349 'The revision does not belong to the given page.'
353 if ( $revision && !( $options & self::OPT_NO_AUDIENCE_CHECK ) ) {
356 if ( !$revision->audienceCan( RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_PUBLIC ) ) {
357 return Status::newFatal(
358 'missing-revision-permission',
360 $revision->getTimestamp(),
361 $this->titleFormatter->getPrefixedDBkey( $page )
377 private function newPoolWorkArticleView(
380 RevisionRecord $revision,
383 $useCache = $this->shouldUseCache( $page, $revision );
385 switch ( $useCache ) {
386 case self::CACHE_PRIMARY:
387 $this->statsDataFactory->increment(
'ParserOutputAccess.PoolWork.Current' );
388 $primaryCache = $this->getPrimaryCache( $parserOptions );
389 $parserCacheMetadata = $primaryCache->getMetadata( $page );
390 $cacheKey = $primaryCache->makeParserOutputKey( $page, $parserOptions,
391 $parserCacheMetadata ? $parserCacheMetadata->getUsedOptions() : null
394 $workKey = $cacheKey .
':revid:' . $revision->getId();
401 $this->revisionRenderer,
404 $this->chronologyProtector,
406 $this->wikiPageFactory,
407 !( $options & self::OPT_NO_UPDATE_CACHE ),
408 (
bool)( $options & self::OPT_LINKS_UPDATE )
411 case self::CACHE_SECONDARY:
412 $this->statsDataFactory->increment(
'ParserOutputAccess.PoolWork.Old' );
413 $secondaryCache = $this->getSecondaryCache( $parserOptions );
414 $workKey = $secondaryCache->makeParserOutputKey( $revision, $parserOptions );
420 $this->revisionRenderer,
425 $this->statsDataFactory->increment(
'ParserOutputAccess.PoolWork.Uncached' );
426 $secondaryCache = $this->getSecondaryCache( $parserOptions );
427 $workKey = $secondaryCache->makeParserOutputKeyOptionalRevId( $revision, $parserOptions );
432 $this->revisionRenderer,
441 if ( $pOpts->getUseParsoid() ) {
446 return $this->parserCacheFactory->getParserCache(
447 'parsoid-' . ParserCacheFactory::DEFAULT_NAME
451 return $this->parserCacheFactory->getParserCache(
452 ParserCacheFactory::DEFAULT_NAME
456 private function getSecondaryCache(
ParserOptions $pOpts ): RevisionOutputCache {
457 if ( $pOpts->getUseParsoid() ) {
458 return $this->parserCacheFactory->getRevisionOutputCache(
459 'parsoid-' . ParserCacheFactory::DEFAULT_RCACHE_NAME
463 return $this->parserCacheFactory->getRevisionOutputCache(
464 ParserCacheFactory::DEFAULT_RCACHE_NAME
__construct(ParserCacheFactory $parserCacheFactory, RevisionLookup $revisionLookup, RevisionRenderer $revisionRenderer, IBufferingStatsdDataFactory $statsDataFactory, ILBFactory $lbFactory, ChronologyProtector $chronologyProtector, LoggerSpi $loggerSpi, WikiPageFactory $wikiPageFactory, TitleFormatter $titleFormatter)
PoolWorkArticleView for the current revision of a page, using ParserCache.
PoolWorkArticleView for an old revision of a page, using a simple cache.
PoolCounter protected work wrapping RenderedRevision->getRevisionParserOutput.