3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
21 namespace MediaWiki\PoolCounter
;
23 use InvalidArgumentException
;
24 use MediaWiki\Logger\Spi
as LoggerSpi
;
25 use MediaWiki\MainConfigNames
;
26 use MediaWiki\MediaWikiServices
;
27 use MediaWiki\Page\PageRecord
;
28 use MediaWiki\Page\WikiPageFactory
;
29 use MediaWiki\Parser\ParserCache
;
30 use MediaWiki\Parser\ParserOptions
;
31 use MediaWiki\Parser\ParserOutput
;
32 use MediaWiki\Revision\RevisionRecord
;
33 use MediaWiki\Revision\RevisionRenderer
;
34 use MediaWiki\Status\Status
;
35 use MediaWiki\Utils\MWTimestamp
;
36 use Wikimedia\Rdbms\ChronologyProtector
;
37 use Wikimedia\Rdbms\ILBFactory
;
40 * PoolWorkArticleView for the current revision of a page, using ParserCache.
44 class PoolWorkArticleViewCurrent
extends PoolWorkArticleView
{
47 /** @var PageRecord */
49 /** @var ParserCache */
51 /** @var ILBFactory */
53 /** @var WikiPageFactory */
54 private $wikiPageFactory;
55 /** @var bool Whether it should trigger an opportunistic LinksUpdate or not */
56 private bool $triggerLinksUpdate;
57 private ChronologyProtector
$chronologyProtector;
60 * @param string $workKey
61 * @param PageRecord $page
62 * @param RevisionRecord $revision Revision to render
63 * @param ParserOptions $parserOptions ParserOptions to use for the parse
64 * @param RevisionRenderer $revisionRenderer
65 * @param ParserCache $parserCache
66 * @param ILBFactory $lbFactory
67 * @param ChronologyProtector $chronologyProtector
68 * @param LoggerSpi $loggerSpi
69 * @param WikiPageFactory $wikiPageFactory
70 * @param bool $cacheable Whether it should store the result in cache or not
71 * @param bool $triggerLinksUpdate Whether it should trigger an opportunistic LinksUpdate or not
73 public function __construct(
76 RevisionRecord
$revision,
77 ParserOptions
$parserOptions,
78 RevisionRenderer
$revisionRenderer,
79 ParserCache
$parserCache,
80 ILBFactory
$lbFactory,
81 ChronologyProtector
$chronologyProtector,
83 WikiPageFactory
$wikiPageFactory,
84 bool $cacheable = true,
85 bool $triggerLinksUpdate = false
87 // TODO: Remove support for partially initialized RevisionRecord instances once
88 // Article no longer uses fake revisions.
89 if ( $revision->getPageId() && $revision->getPageId() !== $page->getId() ) {
90 throw new InvalidArgumentException( '$page parameter mismatches $revision parameter' );
93 parent
::__construct( $workKey, $revision, $parserOptions, $revisionRenderer, $loggerSpi );
95 $this->workKey
= $workKey;
97 $this->parserCache
= $parserCache;
98 $this->lbFactory
= $lbFactory;
99 $this->chronologyProtector
= $chronologyProtector;
100 $this->wikiPageFactory
= $wikiPageFactory;
101 $this->cacheable
= $cacheable;
102 $this->triggerLinksUpdate
= $triggerLinksUpdate;
108 public function doWork() {
109 // T371713: Temporary statistics collection code to determine
110 // feasibility of Parsoid selective update
111 $sampleRate = MediaWikiServices
::getInstance()->getMainConfig()->get(
112 MainConfigNames
::ParsoidSelectiveUpdateSampleRate
114 $doSample = ( $sampleRate && mt_rand( 1, $sampleRate ) === 1 );
116 $previousOutput = null;
117 if ( $this->parserOptions
->getUseParsoid() ||
$doSample ) {
118 // Parsoid can do selective updates, so it is worth checking the
119 // cache for an existing entry. Not worth it for the legacy
121 $previousOutput = $this->parserCache
->getDirty( $this->page
, $this->parserOptions
) ?
: null;
123 $status = $this->renderRevision( $previousOutput, $doSample, 'PoolWorkArticleViewCurrent' );
124 /** @var ParserOutput|null $output */
125 $output = $status->getValue();
128 if ( $this->cacheable
&& $output->isCacheable() ) {
129 $this->parserCache
->save(
136 if ( $this->triggerLinksUpdate
) {
137 $this->wikiPageFactory
->newFromTitle( $this->page
)->triggerOpportunisticLinksUpdate( $output );
145 * @return Status|false
147 public function getCachedWork() {
148 $parserOutput = $this->parserCache
->get( $this->page
, $this->parserOptions
);
150 $logger = $this->loggerSpi
->getLogger( 'PoolWorkArticleView' );
151 $logger->debug( $parserOutput ?
'parser cache hit' : 'parser cache miss' );
153 return $parserOutput ? Status
::newGood( $parserOutput ) : false;
157 * @param bool $fast Fast stale request
158 * @return Status|false
160 public function fallback( $fast ) {
161 $parserOutput = $this->parserCache
->getDirty( $this->page
, $this->parserOptions
);
163 $logger = $this->loggerSpi
->getLogger( 'dirty' );
165 if ( !$parserOutput ) {
166 $logger->info( 'dirty missing' );
171 /* Check if the stale response is from before the last write to the
172 * DB by this user. Declining to return a stale response in this
173 * case ensures that the user will see their own edit after page
176 * Note that the CP touch time is the timestamp of the shutdown of
177 * the save request, so there is a bias towards avoiding fast stale
178 * responses of potentially several seconds.
180 $lastWriteTime = $this->chronologyProtector
->getTouched( $this->lbFactory
->getMainLB() );
181 $cacheTime = MWTimestamp
::convert( TS_UNIX
, $parserOutput->getCacheTime() );
182 if ( $lastWriteTime && $cacheTime <= $lastWriteTime ) {
184 'declining to send dirty output since cache time ' .
185 '{cacheTime} is before last write time {lastWriteTime}',
187 'workKey' => $this->workKey
,
188 'cacheTime' => $cacheTime,
189 'lastWriteTime' => $lastWriteTime,
192 // Forget this ParserOutput -- we will request it again if
193 // necessary in slow mode. There might be a newer entry
194 // available by that time.
199 $logger->info( $fast ?
'fast dirty output' : 'dirty output', [ 'workKey' => $this->workKey
] );
201 $status = Status
::newGood( $parserOutput );
202 $status->warning( 'view-pool-dirty-output' );
203 $status->warning( $fast ?
'view-pool-contention' : 'view-pool-overload' );
209 /** @deprecated class alias since 1.42 */
210 class_alias( PoolWorkArticleViewCurrent
::class, 'PoolWorkArticleViewCurrent' );